<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>poArlim</title>
    <link>https://poalim.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Sun, 9 Aug 2026 19:33:26 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>aa_rong_blog</managingEditor>
    <item>
      <title>Kubectl 명령어 간편하게 사용하기 (alias, 자동 완성, flag)</title>
      <link>https://poalim.tistory.com/60</link>
      <description>&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;쿠버네티스를 사용하면서 가장 자주 사용하는 명령어는 단연코 &lt;b&gt;kubectl &lt;/b&gt;일 것이다. 이 길다면 긴 명령어를 매번 치기는 번거로울뿐더러, 긴 Pod 이름 등을 매 번 복사 붙여 넣기 하기도 여간 귀찮은 게 아니다.&lt;br /&gt;개발 편의성을 매우 중시하는 개발자들을 위해 kubectl 명령어를 좀 더 편하게 사용하기 위해 연구해 보자.&lt;/p&gt;
&lt;h2 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size26&quot;&gt;1. Alias&lt;/h2&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;우선 'kubectl' 명령어의 길이 자체가 너무 길다! 적당히 헷갈리지 않으면서도 짧은 alias를 등록해 두고 사용하자. 보통 'k'를 많이 쓰는 것 같다.&lt;br /&gt;현재 등록된 alias 목록을 보고 싶다면, bash 창에 `alias` 명령어를 입력하면 된다. alias를 신규로 등록하는 것 또한 간단하게 &lt;b&gt;alias 별칭='명령어'&lt;/b&gt;를 사용하면 된다. (일회성 등록)&lt;/p&gt;
&lt;pre class=&quot;shell&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;alias k='kubectl'&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;일회성이 아니라 지속 적용되게 하려면 홈 디렉터리에 있는 .bashrc 파일 등록해서 사용하면 된다.&lt;/p&gt;
&lt;pre class=&quot;shell&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;# .bashrc 파일 오픈
vim ~/.bashrc

# .bashrc 파일 내에 alias 명령어 추가
alias k='kubectl'

# 변경 사항 적용
source ~/.bashrc&lt;/code&gt;&lt;/pre&gt;
&lt;h2 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size26&quot;&gt;2. 자동 완성&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;bash의 자동 완성을 담당하는 bash-completion 이 설치되어 있지 않다면 kubectl 자동 완성 또한 적용되지 않는다.&lt;br /&gt;보통 설치되어 있지만, 설치되어 있지 않을 경우(type _init_completion 명령어로 확인 가능)&lt;br /&gt;`apt-get install bash-completion` 등의 패키지 관리자 명령어를 통해 설치해 주자.&lt;/blockquote&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;kubectl 자동 완성은 kubectl completion bash 명령으로 생성할 수 있다. (&lt;a href=&quot;https://kubernetes.io/ko/docs/tasks/tools/included/optional-kubectl-configs-bash-linux/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;color: #0070d1;&quot;&gt;참고&lt;/span&gt;&lt;/a&gt;)&lt;br /&gt;alias와 마찬가지로 .bashrc 파일에 등록해 주자.&lt;/p&gt;
&lt;pre class=&quot;shell&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;# .bashrc 파일 오픈
vim ~/.bashrc

# .bashrc 파일 내에 자동 완성 명령어 추가
source &amp;lt;(kubectl completion bash)

# 변경 사항 적용
source ~/.bashrc&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;이 자동 완성은 kubectl이라는 명령어를 인식해야만 작동하기 때문에, 위에서 만든 alias(k)에는 적용이 되지 않는다.&amp;nbsp;&lt;b&gt;alias에도 자동 완성을 적용하고 싶다면&lt;/b&gt; 마찬가지로 `~/.bashrc`에 아래 내용을 추가로 기입 &amp;amp; 적용해주자.&lt;/p&gt;
&lt;pre class=&quot;shell&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;complete -o default -F __start_kubectl k&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;여기까지 적용을 했다면, 'k g&amp;lt;tab&amp;gt;' 을 눌러보면 'k get'으로 자동 완성이 되는 것을 확인할 수 있을 것이다. (이외의 명령어들 및 pod 이름 등에도 모두 적용된다.)&lt;/p&gt;
&lt;h2 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size26&quot;&gt;3. Flag 옵션이 있을 때 자동 완성 적용&lt;/h2&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;업무 환경에서 위 내용을 실제로 사용하다 보니 사소한 문제가 있었다. 종종 context, kubeconfig 등 설정이 필요한 경우에 kubectl과 함께 아래 예시처럼 alias로 묶어 놓고 사용하는 경우가 있다. (상황에 따라 다른 kubectl 명령어 만으로 다른 config 파일 및 context를 사용할 수 있어서 편리하다.)&lt;/p&gt;
&lt;pre class=&quot;shell&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;alias k1='kubectl --context context1'
alias k2='kubectl --context context2'
alias k3='kubectl --kubeconfig /home/poarlim/.kube/customconfig'&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;이처럼&amp;nbsp;&lt;b&gt;flag 옵션을 alias로 같이 사용했을 때 자동 완성 스크립트가 동작하지 않는 현상&lt;/b&gt;이 있는 것을 발견했다.&lt;/p&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;원인&lt;/h4&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/kubernetes/kubectl/issues/1119&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;bash completions stop working when aliasing kubectl --kubeconfig on v1.22&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;정리하자면, 'kubectl des&amp;lt;tab&amp;gt;'을 입력하면 'kubectl &lt;b&gt;__complete&lt;/b&gt; des'으로 __complete 플러그인이 kubectl 뒤에 추가되어 자동 완성을 활성화시켜 주지만, alias 사용 시 'k1 des&amp;lt;tab&amp;gt;'을 입력하면 'kubectl --context context1 &lt;b&gt;__complete&lt;/b&gt; des'으로 __complete 플러그인이 이상한 위치에 붙게 되어서 kubectl 명령어를 인식하지 못하게 된다.&lt;/p&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;해결&lt;/h4&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/kubernetes/kubernetes/pull/105141&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;Do not try to load plugins for cobra commands&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;해결책은 위 이슈에서 찾을 수 있었는데,&lt;b&gt; '=' 기호를 사용하여 flag를 추가하면 자동 완성을 인식하도록 이슈를 해결&lt;/b&gt;했다고 한다. (모든 경우에 가능하게 하는 것은 너무 복잡해진다고...)&lt;br /&gt;&lt;b&gt;즉, flag가 있는 alias에 자동 완성을 적용하려면 &lt;span style=&quot;color: #ee2323;&quot;&gt;띄어쓰기가 없어야 하고, '='을 사용&lt;/span&gt;해야 한다.&amp;nbsp;&lt;/b&gt;이에 맞도록 alias를 수정하도록 하자.&lt;/p&gt;
&lt;pre class=&quot;shell&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;alias k1='kubectl --context=context1'
alias k2='kubectl --context=context2'
alias k3='kubectl --kubeconfig=/home/poarlim/.kube/customconfig'&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;alias 수정 후 'source ~/.bashrc'로 적용하여 주면 자동 완성이 잘 적용된다.&lt;br /&gt;※&lt;i&gt; --kubeconfig를 사용할 때는 항상 전체 경로를 사용해야 자동 완성이 적용된다. (상대 경로 X)&lt;/i&gt;&lt;/p&gt;</description>
      <category>Programming/Server</category>
      <category>Alias</category>
      <category>context</category>
      <category>flag</category>
      <category>k8s</category>
      <category>kubeconfig</category>
      <category>kubectl</category>
      <category>자동 완성</category>
      <author>aa_rong_blog</author>
      <guid isPermaLink="true">https://poalim.tistory.com/60</guid>
      <comments>https://poalim.tistory.com/60#entry60comment</comments>
      <pubDate>Wed, 3 Jul 2024 16:55:11 +0900</pubDate>
    </item>
    <item>
      <title>RestClient 알아보기 (RestTemplate이 Deprecated 된다고요?)</title>
      <link>https://poalim.tistory.com/59</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Boot 3.2에 새롭게 추가된 RestClient에 대해 알아보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1030&quot; data-origin-height=&quot;30&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/client/RestTemplate.html&quot; target=&quot;_blank&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mkNAR/btsIiipPhIB/KVDR2DutkSm6QSYuhRCEX1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmkNAR%2FbtsIiipPhIB%2FKVDR2DutkSm6QSYuhRCEX1%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;860&quot; height=&quot;25&quot; data-origin-width=&quot;1030&quot; data-origin-height=&quot;30&quot;/&gt;&lt;/a&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot;&gt;spring docs의 RestTemplate에 대한 설명에 위와 같은 NOTE가 추가되었다. &lt;u&gt;Spring 6.1(Spring Boot 3.2) 버전부터는 RestClient가 더 모던한 API를 제공한다&lt;/u&gt;는 말이다. 그렇다면 RestTemplate은 사용하면 안 되는 것일까?&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;RestClient알아보기-RestTemplate이Deprecated된다?&quot; style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;RestTemplate이 Deprecated 된다?&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;RestTemplate이 Deprecated 된다고 알고 있는 사람이 종종 있는데, 현재 RestTemplate의 JavaDoc에는 그런 말을 전혀 찾아볼 수 없었다. &lt;/b&gt;이 말은 어디서 나온 것일까? 스프링 개발진의 의도를 유추해볼 수 있는 흐름을 찾아보았다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot;&gt;RestTemplate의 Deprecated에 대한 언급이 처음 나온 것은 Spring 5.1 RC2 Release에서 &lt;a href=&quot;https://github.com/spring-projects/spring-framework/issues/21531&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Update docs on RestTemplate to indicate it's superceded by WebClient&lt;/a&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;이라는 이슈로 등장하였다. RestTemplate을 WebClient로 대체할 것이라는 말이고, 변경된 docs를 보면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;RestTemplate이 deprecated될 것이며, WebClient로 대체된다&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot;&gt;는 언급이 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;591&quot; data-origin-height=&quot;120&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qZZcy/btsIgLNEBx2/ta9jNjNWldKbXNdnxDz8IK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qZZcy/btsIgLNEBx2/ta9jNjNWldKbXNdnxDz8IK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qZZcy/btsIgLNEBx2/ta9jNjNWldKbXNdnxDz8IK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqZZcy%2FbtsIgLNEBx2%2Fta9jNjNWldKbXNdnxDz8IK%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;591&quot; height=&quot;120&quot; data-origin-width=&quot;591&quot; data-origin-height=&quot;120&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/spring-projects/spring-framework/commit/91d17cf62882a436d506694ee840d59a2d751fed#diff-9a340cc500444b1d71574d2cd7df8d3c39aed7469a176c9247141695318b212a&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Commit : Add notes on future deprecation of the RestTemplate&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot;&gt;이러한 이슈로 인해 여러 개발자들이 RestTemplate이 Deprecated 될 것이고, WebClient를 사용해야 한다고 알고 있는 경우가 많다. 하지만, WebClient는 Web MVC 스택의 개발 환경에는 맞지 않을 뿐더러(&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #a5adba; text-align: start;&quot;&gt;block()을 사용하여 동기로 동작하게 할 수는 있다&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot;&gt;), WebFlux 라이브러리를 통으로 추가해줘야 사용할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot;&gt;이러한 상황을 의식하였는지, Spring 5.2.4에서 주석이 업데이트 된다. (&lt;a href=&quot;https://github.com/spring-projects/spring-framework/issues/24503&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Update advice on RestTemplate&lt;/a&gt;)&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;574&quot; data-origin-height=&quot;219&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ct3wH5/btsIiNXgHYX/pBidBKszmwIGGASUpUc5c0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ct3wH5/btsIiNXgHYX/pBidBKszmwIGGASUpUc5c0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ct3wH5/btsIiNXgHYX/pBidBKszmwIGGASUpUc5c0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fct3wH5%2FbtsIiNXgHYX%2FpBidBKszmwIGGASUpUc5c0%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;574&quot; height=&quot;219&quot; data-origin-width=&quot;574&quot; data-origin-height=&quot;219&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Deprecated &amp;rarr; Maintenance mode&lt;/b&gt;로 말이 바뀌었다. 이 때까지, 스프링 개발진은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;RestTemplate을 더 이상 유지할 생각이 없어 보이며 WebClient를 사용하도록 권장&lt;/b&gt;하고 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이후, 드디어 Spring 6 버전이 릴리즈 됐고, Spring 6.1에 RestClient가 추가되었다! 이 때, RestTemplate의 docs는 아래와 같이 변경되었다. (&lt;a href=&quot;https://github.com/spring-projects/spring-framework/commit/8691173fd8e1397f66f7fbac02913e1603c2675f&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Create RestClient documentation placeholder&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;560&quot; data-origin-height=&quot;160&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dTLMrd/btsIiopSFHW/BWsRu0nEpHVHQCVC2ctbFK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dTLMrd/btsIiopSFHW/BWsRu0nEpHVHQCVC2ctbFK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dTLMrd/btsIiopSFHW/BWsRu0nEpHVHQCVC2ctbFK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdTLMrd%2FbtsIiopSFHW%2FBWsRu0nEpHVHQCVC2ctbFK%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;560&quot; height=&quot;160&quot; data-origin-width=&quot;560&quot; data-origin-height=&quot;160&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;더 모던한 API인 RestClient가 추가되었다.&lt;/b&gt;&amp;nbsp;더불어,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;RestTemplate이 maintenance mode 라는 말이 빠졌다.&lt;span&gt;&amp;nbsp; &lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;마지막으로, 최근(Jan 2024)에 추가된 docs는 아래와 같다. (&lt;a href=&quot;https://github.com/spring-projects/spring-framework/commit/b16f379788b8b56e4a744e5645f8f524f343da57&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Improve RestTemplate Javadoc&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;648&quot; data-origin-height=&quot;158&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pwMvw/btsIhS532D1/LklcKfH8ijAcVKNjSoiRo0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pwMvw/btsIhS532D1/LklcKfH8ijAcVKNjSoiRo0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pwMvw/btsIhS532D1/LklcKfH8ijAcVKNjSoiRo0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpwMvw%2FbtsIhS532D1%2FLklcKfH8ijAcVKNjSoiRo0%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;648&quot; height=&quot;158&quot; data-origin-width=&quot;648&quot; data-origin-height=&quot;158&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;RestTemplate과 RestClient은 동일한 기반을 공유하지만, RestClient가 좀 더 새로운 고수준 기능에 초점을 맞추고 있다&lt;/b&gt;고 한다. 이러한 흐름을 봤을 때,&lt;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;RestTemplate이 실제로 사라지기까지는 아직 꽤 시간이 남은 것 같다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;다만, RestClient라는 더 모던한 대체재가 생겼으니 사용을 권고하는 정도인 것 같다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;실제로, RestTemplate에 대한 추후 계획을 묻는 issue에서 이러한 답변을 볼 수 있다. (&lt;a href=&quot;https://github.com/spring-projects/spring-framework/issues/32016&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Plan for Synchorized Rest Clients RestTemplate&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;746&quot; data-origin-height=&quot;86&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bsxEnN/btsIiRFnBFS/X5XnrrfJACBpaXVTXjTCdK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bsxEnN/btsIiRFnBFS/X5XnrrfJACBpaXVTXjTCdK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bsxEnN/btsIiRFnBFS/X5XnrrfJACBpaXVTXjTCdK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbsxEnN%2FbtsIiRFnBFS%2FX5XnrrfJACBpaXVTXjTCdK%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;746&quot; height=&quot;86&quot; data-origin-width=&quot;746&quot; data-origin-height=&quot;86&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;RestClient알아보기-왜RestClient가필요하였을까?&quot; style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;왜 RestClient가 필요하였을까?&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;더 모던한 API가 제공된 배경은 이러하다. Spring Web MVC 스택의 개발에서 REST API 호출을 위해서는&lt;span&gt;&amp;nbsp;&lt;/span&gt;RestTemplate을 사용해왔다. 하지만, Spring 5.x에서 소개된 Spring WebFlux에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;WebClient라는 도구가 제공되었고, 이는 코드의 사용성과 유연성을 향상 시키고 가독성을 좋게 만들었다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;하지만, WebClient는 WebFlux에 기반한 비동기 HTTP Client이기 때문에 Web MVC에서 사용하려면 block()으로 동기 처리를 해주는 등의 처리가 필요하였고, WebClient 하나만을 위해서 spring-webflux 라이브러리를 추가해주어야 하는 번거로움이 있었다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이러한 이유로 Spring 6.1에서 RestClient가 등장하였다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;RestClient는 동기식 HTTP Client이며, WebClient의 fluent API와 RestTemplate의 인프라를 결합한 것&lt;/b&gt;으로 HttpMessageConverter, ClientHttpRequestFactory, ClientHttpRequestInterceptor 등 RestTemplate의 기반 구성 요소를 그대로 사용한다. 이러한 새로운 기능은 Spring Web MVC 스택의 개발에서 fluent, functional 한 스타일의 개발을 가능하게 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;RestClient알아보기-RestClientvsRestTemplate&quot; style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;RestClient vs RestTemplate&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;RestClient는 다음과 같은 모던한(?) 체이닝 방식으로 API를 호출한다.&lt;/p&gt;
&lt;pre id=&quot;code_1719571945380&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;RestClient restClient = RestClient.create();
 
String result = restClient.get()
    .uri(&quot;https://example.com&quot;)
    .accept(MediaType.APPLICATION_JSON)
    .retrieve()
    .body(String.class);&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이를 RestTemplate으로 구현하면 아래와 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1719571974003&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.put(HttpHeaders.ACCEPT, Collections.singletonList(MediaType.APPLICATION_JSON_VALUE));
HttpEntity&amp;lt;String&amp;gt; entity = new HttpEntity&amp;lt;&amp;gt;(headers);
 
String result = restTemplate.exchange(&quot;https://example.com&quot;, HttpMethod.GET, entity, String.class).getBody();&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;깔끔한 생김새로 편하게 사용할 수 있을 것 같다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/reference/integration/rest-clients.html#_migrating_from_resttemplate_to_restclient&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;RestTemplate to RestClient Migrate&lt;/a&gt;은 연결된 링크에 각 메서드 별로 정리되어 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;RestClient알아보기-RestClient사용&quot; style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;RestClient 사용&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;RestClient의 사용법을 간단히 알아보자. (자세한 사용법은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/reference/integration/rest-clients.html&quot;&gt;https://docs.spring.io/spring-framework/reference/integration/rest-clients.html&lt;/a&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;를 참고하면 된다.)&lt;/p&gt;
&lt;h3 id=&quot;RestClient알아보기-RestClient생성&quot; style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;RestClient 생성&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;RestClient을 생성하는 방법은 크게 아래와 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기본 RestClient 생성 : create()&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1719572084956&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;RestClient defaultClient = RestClient.create();&lt;/code&gt;&lt;/pre&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;background-color: #ffffff; color: #172b4d; text-align: left;&quot;&gt;RestTemplate을 사용하여 RestClient 생성(기존에 사용하던 RestTemplate을 그대로 사용하여 RestClient를 만들 수 있다.)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1719572131899&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;RestClient restClient = RestClient.create(myCustomRestTemplate);&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;커스텀 RestClient 생성&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #172b4d; text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: RestClient.builder 사용&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1719572177789&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;RestClient customClient = RestClient.builder()
    .requestFactory(new HttpComponentsClientHttpRequestFactory())
    .messageConverters(converters -&amp;gt; converters.add(new MyCustomMessageConverter()))
    .baseUrl(&quot;https://example.com&quot;)
    .defaultUriVariables(Map.of(&quot;variable&quot;, &quot;foo&quot;))
    .defaultHeader(&quot;My-Header&quot;, &quot;Foo&quot;)
    .requestInterceptor(myCustomInterceptor)
    .requestInitializer(myCustomInitializer)
    .build();&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;RestClient는 RestTemplate의 인프라를 공유하기 때문에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;메시지 컨버터, 요청 팩토리, 인터셉터 등&lt;/b&gt;은 모두&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;RestTemplate와 동일하게 커스텀하여 생성이 가능&lt;/b&gt;하다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;싱글톤으로 사용하려면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Spring Boot에서 자동으로 주입해주는&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;RestClient.Builder Bean을&amp;nbsp;사용&lt;/b&gt;하면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1719572225077&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Bean
public RestClient restClient(RestClient.Builder builder) {
    return builder.build();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;RestClient알아보기-RestClient사용.1&quot; style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;RestClient 사용&lt;/h3&gt;
&lt;h4 id=&quot;RestClient알아보기-GET요청&quot; style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;GET 요청&lt;/h4&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;RestClient를 이용한 GET 요청은 다음과 같이 사용할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1719572293093&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;RestClient restClient = RestClient.create();
 
String result = restClient.get()
    .uri(&quot;https://example.com&quot;)
    .retrieve()
    .body(String.class);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot;&gt;method() 를 사용하여 HttpMethod를 직접 입력할 수도 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1719572307165&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;String result = restClient.method(HttpMethod.GET)
    .uri(&quot;https://example.com&quot;)
    .retrieve()
    .body(String.class);&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;요청 상태 코드나 헤더 등이 필요하다면, ResponseEntity로 반환 받을 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1719572323122&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ResponseEntity response = restClient.get()
    .uri(&quot;https://example.com&quot;)
    .retrieve()
    .toEntity(String.class);
 
System.out.println(&quot;Response status: &quot; +  response.getStatusCode());
System.out.println(&quot;Response headers: &quot; +  response.getHeaders());
System.out.println(&quot;Contents: &quot; +  response.getBody());&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;RestTemplate과 동일하게 Json 변환을 지원하며, 내부적으로 message converters를 사용한다. 따라서 클래스로 변환하여 값을 반환 받을 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1719572346722&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;User response = restClient.get()
    .uri(&quot;https://example.com&quot;)
    .accept(MediaType.APPLICATION_JSON)
    .retrieve()
    .body(User.class);&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;RestClient알아보기-POST요청&quot; style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;POST 요청&lt;/h4&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;POST 요청은 다음과 같이 작성할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1719572383667&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;User user = ...
 
ResponseEntity resonse = restClient.post()
    .uri(&quot;https://example.com&quot;)
    .contentType(MediaType.APPLICATION_JSON)
    .body(user)
    .retrieve()
    .toBodilessEntity();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot;&gt;put(), delete(), head(), patch() 등도 가능하다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 id=&quot;RestClient알아보기-에러핸들링&quot; style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;에러 핸들링&lt;/h4&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;기본적으로 4xx 또는 5xx 상태 코드를 수신하면 RestClientException의 하위 클래스를 throw한다. 해당 동작은 Ststus Handler를 사용하여 재정의 할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1719572412695&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;String result = restClient.get()
    .uri(&quot;https://example.com&quot;)
    .retrieve()
    .onStatus(HttpStatusCode::is4xxClientError, (request, response) -&amp;gt; {
        throw new customRestClientException(response.getStatusCode(), response.getHeaders())
    })
    .body(String.class);&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;특정 에러 코드만 처리할 수도 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1719572442302&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;String result = restClient.get()
    .uri(&quot;https://example.com&quot;)
    .retrieve()
    .onStatus(status -&amp;gt; status.value() == 404, (request, response) -&amp;gt; {
        throw new customRestClientException(response.getStatusCode(), response.getHeaders())
    })
    .body(String.class);&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;RestClient알아보기-Exchage&quot; style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Exchage&lt;/h4&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;exchange 메서드를 사용하여 request, response에 직접 접근하여 더욱 advanced 한 시나리오를 다룰 수 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;exchange 메서드를 사용하면, 이미 response에 대한 접근을 제공하므로 Status Handler는 적용되지 않는다.&lt;/p&gt;
&lt;pre id=&quot;code_1719573430196&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;User result = restClient.get()
    .uri(&quot;https://example.com&quot;)
    .accept(MediaType.APPLICATION_JSON)
    .exchange((request, response) -&amp;gt; {
        if (response.getStatusCode().is4xxClientError()) {
            throw ...
        }
        else {
            User user = convertResponse(response);
            return user;
        }
    });&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;RestClient알아보기-RestClient튜닝&quot; style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;RestClient 튜닝&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;RestClient 뿐 아니라 RestTemplate에도 동일하게 적용되는 내용이다.&lt;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Spring framework 6.1 버전, Spring Boot 3.2 버전부터, OkHttp3를 지원하지 않는다.&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/spring-projects/spring-framework/issues/30919&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Deprecate OkHttp3ClientHttpRequestFactory&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/spring-projects/spring-boot/issues/36632&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Deprecate support for OkHttp3&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;따라서 그 동안&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;u&gt;&lt;b&gt;OkHttp3ClientHttpRequestFactory&lt;/b&gt;를 사용해서 Connection Pool 등 튜닝을 하고 있었다면 다른 라이브러리로 교체가 필요하다.&lt;/u&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;RestClient는 다양한 ClientHttpRequestFactory 구현체를 지원하며, RestClient에서 사용할 수 있는 ClientHttpRequestFactory 구현체는 아래와 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;HttpComponentsClientHttpRequestFactory&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;: Apache HTTP Components의 HttpClient를 사용한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;JettyClientHttpRequestFactory&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;: Jetty의 HttpClient를 사용한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;JdkClientHttpRequestFactory&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;: Java의 HttpClient를 사용한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;SimpleClientHttpRequestFactory&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;: 기본 구현체로, JDK의 HttpUrlConnection을 사용한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위 구현체 중에서 어떤 게 사용될 지 결정되는 과정은 아래와 같다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;특정 requestFactory를 지정해줬다면, 해당 requestFactory 사용&lt;/li&gt;
&lt;li&gt;requestFactory를 지정하지 않았다면, 클래스 패스를 뒤져서 존재하는 라이브러리를 사용(HttpComponentsClientHttpRequestFactory &amp;rarr; JettyClientHttpRequestFactory &amp;rarr; JdkClientHttpRequestFactory 순서로 탐색한다.)&lt;/li&gt;
&lt;li&gt;클래스 패스에 존재하는 라이브러리가 없다면 SimpleClientHttpRequestFactory 사용.&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;아래 DefaultRestClientBuilder의 RequestFactory를 세팅해주는 코드를 참고하자.&lt;/p&gt;
&lt;pre id=&quot;code_1719574214868&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// DefaultRestClientBuilder.java
private ClientHttpRequestFactory initRequestFactory() {
    if (this.requestFactory != null) {
        return this.requestFactory;
    }
    else if (httpComponentsClientPresent) {
        return new HttpComponentsClientHttpRequestFactory();
    }
    else if (jettyClientPresent) {
        return new JettyClientHttpRequestFactory();
    }
    else if (jdkClientPresent) {
        // java.net.http module might not be loaded, so we can't default to the JDK HttpClient
        return new JdkClientHttpRequestFactory();
    }
    else {
        return new SimpleClientHttpRequestFactory();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;기본으로 사용되는 SimpleClientHttpRequestFactory는 말 그대로 기본 세팅만 존재하므로 Connection Pool 설정 등 튜닝이나 성능 최적화가 필요하다면 대안이 필요하다. Java 9 이상에서는 기본으로 JdkClientHttpRequestFactory가 로드되지만, 역시 타임 아웃 설정 등 세부 설정이 제한적일 수 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;따라서 &lt;b&gt;선택할 수 있는 경우의 수는 두 가지 정도가 된다&lt;/b&gt;.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HttpComponentsClientHttpRequestFactory&lt;/li&gt;
&lt;li&gt;JettyClientHttpRequestFactory&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;RestClient알아보기-HttpComponentsClientHttpRequestFactory&quot; style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;HttpComponentsClientHttpRequestFactory&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Apache HttpComponents HttpClient5를 사용하고자 한다면 아래 의존성을 포함 시켜야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1719574277500&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;org.apache.httpcomponents.client5&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;httpclient5&amp;lt;/artifactId&amp;gt;
&amp;lt;/dependency&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Apache HttpComponents HttpClient5는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;PoolingHttpClientConnectionManagerBuilder&lt;/b&gt;를 통해 커스텀이 가능하다. (이외 추가적인 설정은&lt;span&gt;&amp;nbsp;&lt;a href=&quot;https://hc.apache.org/httpcomponents-client-5.1.x/current/httpclient5/apidocs/org/apache/hc/client5/http/impl/io/PoolingHttpClientConnectionManagerBuilder.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;참고&lt;/a&gt;&lt;/span&gt;)&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1719574308652&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;PoolingHttpClientConnectionManagerBuilder connectionManagerBuilder = PoolingHttpClientConnectionManagerBuilder.create();
connectionManagerBuilder.setMaxConnTotal(25);
connectionManagerBuilder.setMaxConnPerRoute(5);
 
PoolingHttpClientConnectionManager connectionManager = connectionManagerBuilder.build();
 
HttpClient client = HttpClientBuilder.create().setConnectionManager(connectionManager).build();
 
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(client);&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;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;추가적인 설정을 원하는 경우 PoolingHttpClientConnectionManagerBuilderCustomizer를 구현하여 Bean으로 등록할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;RestClient알아보기-JettyClientHttpRequestFactory&quot; style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;JettyClientHttpRequestFactory&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Eclipse Jetty HttpClient를 사용하고자 한다면 아래 의존성을 포함 시켜야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1719574364431&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;org.eclipse.jetty&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;jetty-client&amp;lt;/artifactId&amp;gt;
&amp;lt;/dependency&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Eclipse Jetty HttpClient는 아래와 같이 커스텀이 가능하다.&lt;/p&gt;
&lt;pre id=&quot;code_1719574438341&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;HttpClient client = new HttpClient();
client.setConnectTimeout(Duration.ofSeconds(30).toMillis());
client.setIdleTimeout(Duration.ofSeconds(30).toMillis());
client.setMaxConnectionPerDestination(64);
client.setMaxRequestsQueuedPerDestination(1024);
 
JettyClientHttpRequestFactory requestFactory = new JettyClientHttpRequestFactory(client);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;RestClient알아보기-reference&quot; style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;reference&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/spring-projects/spring-framework&quot;&gt;https://github.com/spring-projects/spring-framework&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/client/RestTemplate.html&quot;&gt;https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/client/RestTemplate.html&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/client/RestClient.html&quot;&gt;https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/client/RestClient.html&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-boot/docs/3.2.0/reference/html//io.html#io.rest-client.restclient&quot;&gt;https://docs.spring.io/spring-boot/docs/3.2.0/reference/html//io.html#io.rest-client.restclient&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/reference/integration/rest-clients.html#_migrating_from_resttemplate_to_restclient&quot;&gt;https://docs.spring.io/spring-framework/reference/integration/rest-clients.html#_migrating_from_resttemplate_to_restclient&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://zarinfam.medium.com/top-4-exciting-features-coming-in-spring-boot-3-2-cf198fc71965&quot;&gt;https://zarinfam.medium.com/top-4-exciting-features-coming-in-spring-boot-3-2-cf198fc71965&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://digma.ai/blog/restclient-vs-webclient-vs-resttemplate/&quot;&gt;https://digma.ai/blog/restclient-vs-webclient-vs-resttemplate/&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://mangkyu.tistory.com/303&quot;&gt;https://mangkyu.tistory.com/303&lt;/a&gt;&lt;/p&gt;</description>
      <category>Programming/Spring</category>
      <category>java</category>
      <category>RESTClient</category>
      <category>restTemplate</category>
      <category>Spring Boot</category>
      <author>aa_rong_blog</author>
      <guid isPermaLink="true">https://poalim.tistory.com/59</guid>
      <comments>https://poalim.tistory.com/59#entry59comment</comments>
      <pubDate>Sat, 29 Jun 2024 12:05:19 +0900</pubDate>
    </item>
    <item>
      <title>13. 모델링 : 클래스 설계의 토대</title>
      <link>https://poalim.tistory.com/58</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;동작 원리와 구조를 간단하게 설명하기 위해, 사물의 특징과 관계를 그림으로 나타낸 것을&amp;nbsp;&lt;b&gt;모델,&amp;nbsp;&lt;/b&gt;모델을 만드는 것을 &lt;b&gt;모델링&lt;/b&gt;이라고 한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 악마를 불러들이기 쉬운 User 클래스&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그인 사용자를 나타내는 User 클래스는 웹 서비스에서 자주 볼 수 있다. 이러한 User 클래스는 사양 변경이 굉장히 잦아서 여러 문제를 일으키기 쉽다.&lt;/p&gt;
&lt;pre id=&quot;code_1708893007683&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class User {
    int id;
    String name;
    String email;
    String passwordDigest;
    String address;
    String phoneNumber;
    String bio;
    String url;
    int discountPoint;
    String themeMode;
    LocalDate birthday;
    String corporationNumber;	// 법인 등록번호
    // ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 User 코드는 구매자와 더불어 법인 사용자에서도 사용하기 위해 corporationNumber라는 필드를 추가했다. 이 코드는 법인 사용자를 관리하는 CorporationManager에서 등록한 User를 일반 사용자를 관리하는 UserManager에서 오류가 발생하는 등(일반 등록번호 존재X) 문제가 발생할 여지가 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 모델링으로 접근해야 하는 구조&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2.1 시스템이란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사전적 정의는 '&lt;b&gt;수많은 구성 요소로 이루어진 집합체로서, 각가의 부분이 유기적으로 연결되어, 전체적으로 하나의 목적을 갖고 움직이는 것&lt;/b&gt;'이다. 예를 들어, 목적지로 이동하기 위해서, 사람은 두 다리를 번갈아 움직이면서 이동하는 '이족 보행 시스템'을 사용할 수도 있고, 자동차와 비행기 등 다른 시스템을 활용할 수도 있다. 여기서 자동차와 비행기 등의 시스템이 만들어진 이유는, 이 시스템을 활용하여 목적 달성을 더 효율적으로 하기 위해서(더 빨리 이동하기 위해서)이다.&amp;nbsp;시스템은 목적 달성을 위한 수단이다. 즉, &lt;b&gt;기술의 본질은 능력을 확장하는 것&lt;/b&gt;이라고 할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2.2. 시스템 구조와 모델링&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;b&gt;모델이란 특정 목적 달성을 위해 최소한으로 필요한 요서를 갖춘 것이다.&lt;/b&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2.3 소프트웨어 설계와 모델링&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델이란 특정 목적을 달성하기 위해서,&amp;nbsp;&lt;b&gt;최소한&lt;/b&gt;으로 필요한 요소를 갖춘것 이라고 했으므로, 설계 시&amp;nbsp;&lt;b&gt;최소한으로 필요한 요소&lt;/b&gt;들만 포함하여야 한다. 너무 많은 정보를 포함하면 모델의 목적을 알기 힘들어진다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 안 좋은 모델의 문제점과 해결 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에 작성했던 User클래스는 개인 프로필과 관련된 요소인 생년월일 등과 법인 정보 검증과 관련된 요소인 법인 등록번호 등이 혼재되어 있다. 또한, 로그인 인증과 관련된 이메일 주소와 비밀번호도 포함되어 있는&amp;nbsp;&lt;b&gt;일관성 없는 모델&lt;/b&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;PersonalAccount : 개별 로그인 인증&lt;/li&gt;
&lt;li&gt;CorporateAccount : 법인 로그인 인증&lt;/li&gt;
&lt;li&gt;Profile : 특징 표현&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델을 단순한 '대상'으로 해석하면, 여기에 모든 목적이 담기게 되어 데이터가 거대해지고 일관성 없는 구조가 된다. &lt;b&gt;모델은 시스템 전체가 아니라 특정한 목적 달성과 관련된 부분만을 추려 표현한 시스템의 일부&lt;/b&gt;이다. 목적 달성 수단으로 해석해야 제대로 모델링할 수가 있다. 따라서 10장에서 살펴보았던 '&lt;b&gt;목적 중심 이름 설계&lt;/b&gt;'를 잘 수행한다면, 목적을 달성하기에 적절한 모델을 설계할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 8장에서 설명한 '단일 책임 원칙'과도 이어진다. 목적과 책임은 서로 대응 되는 관계이므로, 단일 책임 원칙은 단일 목적 원칙이라고 바꿔 말할 수 있다. '&lt;b&gt;클래스가 이루어야 하는 목적은 반드시 하나여야 한다&lt;/b&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;해당 모델이 달성하려는 목적을 모두 찾아낸다.&lt;/li&gt;
&lt;li&gt;목적별로 모델링을 다시 수정한다.&lt;/li&gt;
&lt;li&gt;목적 중심 이름 설계를 기반으로 모델에 이름을 붙인다.&lt;/li&gt;
&lt;li&gt;모델에 목적 이외의 요소가 들어가 있다면 다시 수정한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;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;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 기능성을 좌우하는 모델링&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기능성이란 소프트웨어의 품질 특성 중 하나로, '고객의 니즈를 만족하는 정도'를 의미한다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;숨어 있는 목적 파악하기&lt;/h4&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;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;매매 계약&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;목적물&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;ID, 계약 체결 일시, 인도 희망일,&amp;nbsp;&lt;b&gt;지불 기일, 결제 방법&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;ID, 상품 ID, 대금, 수량&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상품 구매는 법적으로 매매 계약이므로, 지불 시기와 결제 방법 등 지불 조건이 지정된 내용이 시스템에 반영되어야 한다.&lt;/p&gt;
&lt;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;b&gt;깊은 모델(&lt;/b&gt;&lt;b&gt;deep model)&lt;/b&gt;이라고 부른다. 깊은 모델은 수많은 시행 착오를 거듭하고, 모델을 계속해서 개량하는 과정에서 발상이 전환되고, 이 과정에서 기존의 한계를 극복할 수 있는 깊은 모델이 만들어진다. &lt;b&gt;설계는 한 번 했다고 끝나는 것이 아니라, 매일매일 반복해서 개선하는 것이 중요하다.&lt;/b&gt;&lt;/p&gt;</description>
      <category>Study/내 코드가 그렇게 이상한가요?</category>
      <author>aa_rong_blog</author>
      <guid isPermaLink="true">https://poalim.tistory.com/58</guid>
      <comments>https://poalim.tistory.com/58#entry58comment</comments>
      <pubDate>Mon, 26 Feb 2024 06:18:17 +0900</pubDate>
    </item>
    <item>
      <title>12. 메서드(함수) : 좋은 클래스에는 좋은 메서드가 있다</title>
      <link>https://poalim.tistory.com/57</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;메서드 설계가 좋지 않으면 클래스 설계가 나빠지고, 반대로 메서드 설계가 좋으면 클래스 설계도 좋아진다. 클래스 설계 방법을 염두에 두고, 메서드를 어떻게 설계하면 좋을지 집중적으로 알아보자.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 반드시 현재 클래스의 인스턴스 변수 사용하기&lt;/h2&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;h2 data-ke-size=&quot;size26&quot;&gt;2. 불변을 활용해서 예상할 수 있는 메서드 만들기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메서드가 의도하지 않게 다른 부분에 영향을 주지 않도록, 불변을 활용해서 예상치 못한 동작 자체를 막을 수 있게 설계하자.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 묻지 말고 명령하라&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 클래스가 다른 클래스의 상태를 판단하거나, 상태에 따라 값을 변경하는 등 '다른 클래스를 확인하고 조작하는 메서드 구조'는 응집도가 낮은 구조이다. 주로 사용되는 getter/setter는 '다른 클래스를 확인하고 조작하는 메서드 구조'가 되기 쉽다. '묻지 말고 명령하라'라는 접근 방법을 생각하며, &lt;b&gt;호출하는 쪽에서 복잡한 처리를 하지 않고, 호출되는 메서드 쪽에서 복잡한 처리를 하는 형태로 만드는 것이 좋다.&lt;/b&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 커맨드/쿼리 분리&lt;/h2&gt;
&lt;pre id=&quot;code_1707771911800&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int gainAndGetPoint() {
    point += 10;
    return point;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 상태 변경과 추출을 동시에 하는 코드는 여러 문제의 원인이 되며, 사용하기도 힘든 메서드가 되기 쉽다.&amp;nbsp;&lt;b&gt;커맨드/쿼리 분리(CQS, Command-Query Separation)&lt;/b&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;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;메서드 구분 종류&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;커맨드&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;상태를 변경하는 것&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;쿼리&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;상태를 리턴하는 것&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;모디파이어&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;커맨드와 쿼리를 동시에 하는 것&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;gaminAndGetPoint라는 모디파이어를 커맨드와 쿼리로 분리하여 코드를 단순하게 만들어보자.&lt;/p&gt;
&lt;pre id=&quot;code_1707772146811&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
* 포인트 증가(커맨드)
*/
void gainPoint) {
    point += 10;
}

/**
* 포인트를 리턴(쿼리)
* @return 포인트
*/
int getPoint() {
    return point;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 매개변수&lt;/h2&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;매개변수를 변경하면 값의 의미가 바뀌어서, 어떤 의미를 나타내는지 유추하기 어렵고, 어디에서 변경되었는지 찾긷고 힘들다. final 수식자를 붙여서 불변으로 만들자. 매개변수를 변경하고 싶다면, 불변 지역 변수를 만들고, 여기에 변경 값을 할당하는 형태로 구현한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;플래그 매개변수 사용하지 않기
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;플래그 매개변수를 받는 메서드는 가독성이 낮아지게 만든다. 전략 패턴을 사용하는 등, 다른 구조로 설계를 개선하자.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;null 전달하지 않기
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;null을 활용하는 로직은 NullPointerException이 발생할 수 있으며, 매번 null을 확인하는 로직을 추가해야 한다. 매개변수로 null을 전달하지 않게 설계를 하자.&lt;/li&gt;
&lt;li&gt;null을 전달하지 않게 설계하려면, null에 의미를 부여해서는 안 된다. 예를 들어, 장비를 찾용하지 않은 상태를 null로 나타내지 말고 Equipment.EMPTY 등으로 표현하자.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;출력 매개변수 사용하지 않기
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;출력 매개변수는 응집도가 낮은 구조를 만든다. 매개변수는 입력 값으로 사용하는 것이 기본이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;매개변수는 최대한 적게 사용하기
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메서드에 매개변수가 많다는 것은 메서드가 여러 가지 기능을 처리한다는 의미이다. 메서드가 처리할 게 많아지면, 그만큼 로직이 다양해져 여러 문제를 일으킨다. 매개변수가 많아질 수밖에 없다면, 별도의 클래스로 만드는 방법을 검토해 보자.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 리턴 값&lt;/h2&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;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;Price.add 메서드를 int 자료형을 리턴하도록 만들면, 상품 가격 합계, 할인 금액, 배송비 등 어떤 금액을 의미하는 지 알기 힘들다. 가격을 리턴한다는 의도를 명확히 하기 위해 Price 자료형을 리턴해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;null 리턴하지 않기
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;매개변수로 null을 전달하지 않는 것이 좋듯이, null을 리턴하지 않는 것이 좋다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;오류는 리턴 값으로 리턴하지 말고 예외 발생시키기
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;return new Location(-1,-1)&lt;/li&gt;
&lt;li&gt;위 코드는 위치를 이동시키는 메서드에서 이동 후의 좌표가 잘못된 경우, 오류 값 Location(-1,-1)로 이동시키는 코드이다.&lt;/li&gt;
&lt;li&gt;이러한 구현은 호출하는 쪽에서 '오류가 있을 때, 오류 값으로 Location(-1,-1)을 리턴한다'라는 사실을 알고 있어야 하며, 이를 활용한 오류 처리를 확실하게 해야 한다.&lt;/li&gt;
&lt;li&gt;이렇게 여러 의미를 나타내는 값을&amp;nbsp;&lt;b&gt;중의적(double meaning)&lt;/b&gt;이라고 한다. Location(-1,-1)은 특정 좌표값과 오류를 다루는 두 가지 의미를 동시에 다루는 중의적 값이다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;잘못된 상태에서는 어떠한 관용도 베풀지 않고 곧바로 예외를 발생시키자.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>Study/내 코드가 그렇게 이상한가요?</category>
      <author>aa_rong_blog</author>
      <guid isPermaLink="true">https://poalim.tistory.com/57</guid>
      <comments>https://poalim.tistory.com/57#entry57comment</comments>
      <pubDate>Tue, 13 Feb 2024 06:23:29 +0900</pubDate>
    </item>
    <item>
      <title>11. 주석 작성 방법</title>
      <link>https://poalim.tistory.com/56</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;주석은 코드를 더 쉽게 이해하는 데 도움이 되기 위해 작성하지만, 오히려 읽는 사람을 혼란스럽게 만들 수도 있다. 읽는 사람에게 도움을 주어 유지 보수와 변경의 정확성을 높이는 주석 작성 방법에 대해 알아보자.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 내용이 낡은 주석&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스의 사양은 계속해서 변화한다. 하지만, 업무가 바쁘고 충분히 주의하지 않으면 코드가 변경되면서 주석은 변경되지 않는 경우가 생긴다. 이처럼, &lt;b&gt;정보가 오래되어 구현 상태를 제대로 설명하지 못하는 주석은 내용이 낡은 주석이므로 주의해야 한다.&amp;nbsp;&lt;/b&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실제 코드가 아닌 말 또는 주석은 실제 코드가 아니므로 실제 내용을 100% 설명할 수는 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;로직의 동작을 설명하는 주석은 낡기 쉽다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;코드의 동작을 그대로 설명하는 주석은 코드를 변경할 때마다 주석도 변경해야 한다. 이는 시간이 지나면서 실수로 변경이 누락될 수 있고, 실제 내용과 다르게 바뀔 가능성도 있다.&lt;/li&gt;
&lt;li&gt;또한, 주석을 달았기 때문에 안심하여 클래스와 메서드의 이름을 대충 짓게 되는 문제도 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 주석 때문에 이름을 대충 짓는 예&lt;/h2&gt;
&lt;pre id=&quot;code_1707741682497&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 수면, 마비, 혼란, 석화, 사망 이외의 상황에서 행동 가능
boolean isNotSleepingAndIsNotParalyzedAndIsNotConfusedAndIsNotStoneAndIsNotDead() {
    ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드처럼 주석을 달면, 이후에 행동 불능 상태로 '공포' 등이 추가되었을 때, 주석을 함게 변경해줘야 하고, 주석을 변경했다고 해도 메서드 이름에 '공포'를 나타내는 단어가 들어 있지 않으므로 구현과 이름에 괴리가 생긴다. 이와 같은 메서드는 주석으로 설명을 추가하기보다, 메서드의 이름 자체를 수정하는 것이 좋다.&lt;/p&gt;
&lt;pre id=&quot;code_1707741874892&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;boolean canAct() {
    if (states.contains(StateType.sleeping) ||
    	states.contains(StateType.paralyzed) ||
        states.contains(StateType.confused) ||
        states.contains(StateType.stone) ||
        states.contains(StateType.dead)) {
    	return false;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;메서드의 가독성을 높이면, 주석으로 설명을 추가하지 않아도 된다&lt;/b&gt;. 그러면 내용이 낡은 주석이 생길 가능성도 줄어든다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 의도와 사양 변경 시 주의 사항을 읽는 이에게 전달하기&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;유지 보수할 때 : &lt;b&gt;이 로직은 어떤 의도를 갖고 움직이는가&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;사양을 변경할 때 : &lt;b&gt;안전하게 변경하려면 무엇을 주의해야 하는가&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 주석은 위와 같은 내용을 담는 것이 좋다.&lt;/p&gt;
&lt;pre id=&quot;code_1707742162357&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Member {
    private final States states;
    
    // 고통받는 상태일 때 true를 리턴
    boolean isPainful() {
        // 이후 사양 변경으로 표정 변화를 일으키는 상태를 추가할 경우
        // 이 메서드에 로직을 추가합니다.
        if (states.contains(StateType.poison) ||
            states.contains(StateType.paralyzed) ||
            states.contains(StateType.fear)) {
            return true;
        }
        return false;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 살펴본 주석을 작성하는 규칙을 정리해보면 아래와 같다.&lt;/p&gt;
&lt;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 style=&quot;width: 50%;&quot;&gt;규칙&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;이유&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;로직을 변경할 때는 반드시 주석도 함께 변경한다.&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;주석을 제대로 변경하지 않으면, 실제 로직과 달라져 주석을 읽는 사람에게 혼란을 준다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;로직의 내용을 단순하게 설명하기만 하는 주석은 달지 않는다.&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;실질적으로 가독성을 높이지 않고, 주석 유지 보수가 힘들다. 결과적으로 내용이 낡은 주석이 될 가능성이 높다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;가독성이 나쁜 로직에 설명을 추가하는 주석은 달지 않는다. 대신 로직의 가독성을 높여야 한다.&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;주석 유지 보수가 힘들고, 갱신되지 않아 낡은 주석이 될 가능성이 높다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;로직의 의도와 사양을 변경할 때 주의할 점을 주석으로 달아야 한다.&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;유지 보수와 사양 변경에 도움이 된다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 문서 주석&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot;&gt;프로그래밍 언어에 따라 문서 주석 기능을 제공하는 경우도 있다. 자바에는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;Javadoc&lt;/b&gt;&lt;span style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot;&gt;이 있다. JavaDoc의 형식과 태그에 맞춰서 주석을 작성하면, IDEA 등에서 자동으로 API 문서를 생성해주는 기능을 사용할 수 있고, 에디터 위에 커서를 놓으면 주석 내용이 팝업으로 표시되는 등 여러 장점이 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1707742694101&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Money {
    ...
    /**
    * 금액을 추가합니다.
    *
    * @param other 추가 금액
    * @return 추가 후의 금액
    * @throws IllegalArgumentException 통화 단위가 다르면 발생
    */
    Money add(final Money other) {
        if (!currency.equals(other.currency)) {
            throw new IllegalArgumentException(&quot;통화 단위가 다릅니다.&quot;);
        }
        int added = amount + other.amount;
        return new Money(added, currency);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@param : 매개 변수 설명&lt;/li&gt;
&lt;li&gt;@throw : throw 되는 예외 설명&lt;/li&gt;
&lt;li&gt;@return : 리턴 값 설명&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IntelliJ IDEA의 메뉴에서 Tools &amp;gt; Generate JavaDoc을 선택하면, API 문서를 html로 출력할 수 있다.&lt;/p&gt;</description>
      <category>Study/내 코드가 그렇게 이상한가요?</category>
      <author>aa_rong_blog</author>
      <guid isPermaLink="true">https://poalim.tistory.com/56</guid>
      <comments>https://poalim.tistory.com/56#entry56comment</comments>
      <pubDate>Mon, 12 Feb 2024 21:59:29 +0900</pubDate>
    </item>
    <item>
      <title>10. 이름 설계 : 구조를 파악할 수 있는 이름</title>
      <link>https://poalim.tistory.com/55</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이름을 짓는 기본적인 방법은&amp;nbsp;&lt;b&gt;목적 중심 이름 설계&lt;/b&gt;이다. 이는 소프트웨어가 달성해야 하는 목적을 기반으로 이름을 설계하는 방법으로, 이름에서 목적과 의도를 읽고 이해할 수 있게 설계해아 한다는 것이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 부적절한 이름&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;온라인 쇼핑몰을 예로 들어보자. 흔히 볼 수 있는 좋지 않은 이름 설계는 상품을 그대로 '상품 클래스'라고 이름을 붙이는 것이다. 대부분의 로직이 상품을 중심으로 이루어지는 온라인 쇼핑몰 특성상 '상품 클래스'라고 단순하게 이름을 붙이는 순간, 해당 클래스는 수많은 클래스와 관련 있는 로직을 갖게 되고 점점 거대하고 복잡해진다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1.1 관심사 분리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상품은 예약, 주문, 발송 등 다양한 관심사와 연관이 있다. 즉, 상품 클래스는 다양한 관심사와 결합되어, 온갖 것과 관련된 로직을 품게 되는&amp;nbsp;&lt;b&gt;강한 결합&amp;nbsp;&lt;/b&gt;상태가 된다. 강한 결합을 해소하고, 결합이 느슨하고 응집도가 높은 구조로 만들려면&amp;nbsp;&lt;b&gt;관심사 분리(separation of concerns)&lt;/b&gt;를 할 수 있어야 한다. 따라서 상품 클래스는&amp;nbsp;&lt;b&gt;관심사에 따라서 각각 클래스로 분할&lt;/b&gt;해야 한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1.2 관심사에 맞는 이름 붙이기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관심사에 따라 각각의 클래스로 분할하면 각각의 이름을 붙여줘야 한다. 주문 목적의 상품은&amp;nbsp;&lt;b&gt;'주문 상품'&lt;/b&gt;, 예약에서는&amp;nbsp;&lt;b&gt;'예약 상품'&lt;/b&gt;, 발송에서는&amp;nbsp;&lt;b&gt;'발송 상품'&lt;/b&gt; 등으로 이름을 붙일 수 있다. 각각의 관심사에 따라 분할한 후에는 해당 관심사와 관련된 로직을 내부에 캡슐화하면, 결합도를 낮추고 응집도를 높일 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1.3 포괄적이고 의미가 불분명한 이름&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발 초기에는 포괄적인 이름을 붙이는 경우가 많다. '상품 클래스'와 같이 너무 포괄적인 이름은 내부에 온갖 로직을 구현하게 만든다. 이렇게 이름이 너무 포괄적이라서 목적이 불분명한&amp;nbsp;&lt;b&gt;목적 불분명 객체&lt;/b&gt;는 규모가 커지기 쉽다. 비즈니스 목적에 따라 이름을 붙여 보면서, 관심사 분리를 고려한 이름 설계를 하도록 하자.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 이름 설계하기 - 목적 중심 이름 설계&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그래밍에서 이름을 붙이는 것은 가독성을 높이는 역할 뿐 아니라, 결합이 느슨하고 응집도가 높은 구조를 만드는 데 굉장히 중요한 역할을 한다. 목적 중심 이름 설계는 목적에 맞춰 이름을 설계하는 것이다. 즉, 소프트웨어로 달성하고 싶은 목적과 의도를 이름만으로도 알 수 있게 해야 한다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&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;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;이름과 관계없는 로직을 배제하기 쉬워짐&lt;/li&gt;
&lt;li&gt;클래스가 작아짐&lt;/li&gt;
&lt;li&gt;관계된 클래스 개수가 적으므로, 결합도가 낮아짐&lt;/li&gt;
&lt;li&gt;관계된 클래스 개수가 적으므로, 사양 변경 시 생각해야 하는 영향 범위가 좁음&lt;/li&gt;
&lt;li&gt;목적에 특화된 이름을 갖고 있으므로, 어떤 부분을 변경해야 할 때 쉽게 찾을 수 있음&lt;/li&gt;
&lt;li&gt;개발 생산성이 향상됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;존재가 아니라 목적을 기반으로 하는 이름 생각하기
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;'사람'과 '사용자'처럼 단순하게 인물이 존재하기 때문에 붙인 이름은 &lt;b&gt;존재 기반 이름&lt;/b&gt;이라고 할 수 있다. 온라인 쇼핑의 사용자는 단순한 개인이 아니라 법인일 수도 있으므로 '사용자'는 개인과 법인 모두를 나타내게 되고 이는 쓸데없이 로직을 복잡하게 만들 수 있다.&lt;/li&gt;
&lt;li&gt;구체적인 목적을 알 수 있게 &lt;b&gt;목적 기반 이름&lt;/b&gt;을 짓는 것이 좋다.
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;주소 -&amp;gt; 발송지, 배송지, 업무지&lt;/li&gt;
&lt;li&gt;금액 -&amp;gt; &lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;청구 금액, 소비세액, 연체 보증료, 캠페인 할인 금액&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;사용자 -&amp;gt; 계정, 개인 프로필, 직무&lt;/li&gt;
&lt;li&gt;사용자 이름 -&amp;gt; &lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;계정 이름, 닉네임(별칭), 본명, 법인명&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;상품 -&amp;gt; 입고 상품, 예약 상품, 주문 상품, 발송 상품&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;어떤 비즈니스 목적이 있는 지 분석하기
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;비즈니스 목적에 특화된 이름을 만들려면, 어떤 비즈니스를 하는지 모두 파악해야 한다. 소프트웨어가 추구하는 목적과 내용을 화이트보드나 종이에 그려가며 분석해보자.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;소리 내어 이야기해 보기
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;비즈니스 측면을 잘 이해하고 있는 사람과 이야기하여, 목적과 의도를 잘못 인식하고 있는 부분이 있으면 곧바로 피드백을 받도록 하자. 또한, 이야기를 통해서 좀 더 정확하고 구체적인 비즈니스 목적, 목적에 맞는 이름들을 끌어낼 수 있을 것이다.
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;b&gt;고무 오리 디버깅&lt;/b&gt; : 문제가 발생했을 때, 책상 위의 오리 인형에게 문제를 처음부터 차근차근 설명해 보면서 해결하는 방법(실용주의 프로그래머 20주년 기념판, 'Topic 20 디버깅')&lt;/li&gt;
&lt;li&gt;즉, 프로그래밍에서 어떤 문제가 발생했을 때, 문제를 누군가에게 설명하다 보면 스스로 원인을 깨닫고 해결할 수 있다는 방법.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;이용 약관 읽어보기
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이용 약관에는 서비스와 관련된 규칙이 굉장히 엄격한 표현으로 작성되어 있기 때문에, 이를 활용하면 비즈니스와 관련된 이름을 알 수 있다. '구매자', '판매자', '매매 계약' 등 비즈니스 측면에서 명확한 이름들이 나오기 때문에, 이를 참고하여 명확한 클래스 이름을 붙일 수 있다.&lt;/li&gt;
&lt;/ul&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;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 이름 설계 시 주의 사항&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;이름에 관심 갖기
&lt;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;사양 변경 시 '의미 범위 변경' 경계하기
&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;대화에는 등장하지만 코드에 등장하지 않는 이름 주의하기
&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;수식어를 붙여서 구별해야 하는 경우는 클래스로 만들어 보기
&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;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 의미를 알 수 없는 이름&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;기술 중심 명명
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로그래머는 항상 프로그래밍과 관련된 생각을 하기 때문에, 비즈니스 로직의 이름도 프로그래밍과 관련된 용어에서 유래하는 경우가 많다. 이른 방법을&amp;nbsp;&lt;b&gt;기술 중심 명명&lt;/b&gt;이라고 부른다. 이러한 컴퓨터 기술 용어, 프로그래밍 기술 용어는 비즈니스 목적을 나타내는 이름에는 적합하지 않을 수 있으니 주의해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;로직 구조를 나타내는 이름
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;isMemberHpMoreThanZeroAndIsMemberCanAct... 와 같은 로직 구조를 그대로 나타내는 이름은 무엇을 의도하는 지 알 기 힘들다. 의도와 목적을 이해하기 쉽게 이름을 붙이자. -&amp;gt; canEnchant&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;놀람 최소화 원칙
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;놀람 최소화 원칙(Principle of least astonishment 또는 Rule of least surprice)&lt;/b&gt;는 사용자가 예상하지 못한 놀라움은 최소화하도록 설계해야 한다는 접근 방법이다. 로직에 이름과 관련 없는 로직들이 섞여 들어가 사용자가 놀라게 하는 일을 없게 하자. 로직과 이름 사이에 괴리가 있다면 이름을 수정하거나, 메서드와 클래스를 의도에 맞게 따로 만들도록 하자.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 구조에 악영향을 미치는 이름&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;데이터 클래스처럼 보이는 이름
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;~Info, ~Data(ProductInfo)와 같은 이름의 클래스는 읽는 사람에게 '데이터만 갖는 클래스니까, 로직을 구현하면 안 되는구나'라는 이미지를 심어줄 수 있다. 이러한 이미지는 응집도가 낮은 구조가 되기 쉽기 때문에, 이러한 데이터만 갖는다는 인상을 주는 이름은 피하는 것이 좋다.&lt;/li&gt;
&lt;li&gt;예외적으로 데이터 클래스를 사용하는 경우도 있다. 단순 데이터 전송 용도로 사용되는 디자인 패턴인&amp;nbsp;&lt;b&gt;DTO(Data Transfer Object)&lt;/b&gt;는 값 변경이 필요 없으므로, 인스턴스 변수는 final로 선언하고 생성자에서는 값만 지정해야 한다. 참조 용도로만 사용되어야 하므로, 값을 변경하는 용도로 사용하지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;클래스를 거대하게 만드는 이름
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;~Manager와 같은 이름은 클래스를 점점 더 거대하고 복잡하게 만든다. 예를 들어 MemberManager라는 클래스를 만들면, 멤버의 사양이 변경되거나 추가될 때 무의식적으로 '일단 MemberManager에 로직을 추가하면 된다'고 생각하기 쉽다. 단순하게 '관련이 있으니까 거기에 두었다'가 되는 것이다. Manger, Processor 등과 같이 의미가 넓게 해석되어 클래스를 거대하게 만들 수 있는 이름은 주의가 필요하다. 책무가 다른 로직은 다른 클래스로 정의하자.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;상황에 따라 달라질 수 있는 이름
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;'Account'는 금융 업계에서는 '계좌', 컴퓨터 보안에서는 '로그인 권한'을 뜻한다. 이처럼 상황에 따라 달라질 수 있는 이름을 사용한다면 상황(컨텍스트)에 따라 서로 다른 패키지로 선언하여 사용한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;일련번호 명명
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Class001, method001, method002, ... 등과 같은 일련번호를 붙이는 이름은 지양하자.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 이름을 봤을 때, 위치가 부자연스러운 클래스&lt;/h2&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;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;예를 들어, 적을 의미하는 Enemy 클래스에 addItemToParty라는 메서드가 있을 때, 이 메서드는 Party라는 목적어가 있으므로 Party에 무언가 조작을 가하는 메서드라는 것을 짐작할 수 있고, 이 메서드는 Party 클래스로 이동되어야 한다는 것을 알 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;가능하면 메서드의 이름은 동사 하나로 구성되게 하기
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;관심사가 다른 메서드가 섞이지 못하게 막으려면 되도록 메서드의 이름이 동사 하나로 구성되게 설계하는 것이 좋다.&lt;/li&gt;
&lt;li&gt;'동사 + 목적어' 형태 -&amp;gt; 목적어의 개념을 나타내는 클래스를 따로 만들고, 해당 클래스에 '동사 하나' 형태의 메서드를 추가한다.
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;'addItemToParty'라는 메서드는 파티의 인벤토리에 아이템을 추가하는 메서드이다. 이는 관심사가 섞인 형태이기 때문에, '파티의 인벤토리'라는 개념을 변도의 클래스로 만들고(PartyItems), 인벤토리에 아이템을 추가하는 add 메서드를 정의한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;부적절한 위치에 있는 boolean 메서드
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;boolean 자료형을 리턴하는 메서드도 적절하지 않은 클래스에 정의되어 있는 경우가 많다. 메서드가 정의되어 있는 클래스 위치가 적절한지 쉽게 확인하려면, 간단하게 영어로 바꾸어보자.
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;Common 클래스에 isMemberInConfusion : Common is member in confusion.&lt;/li&gt;
&lt;li&gt;Member 클래스로 이동된 isInConfusion : Member is in confusion. -&amp;gt; 의미적으로 이게 자연스러움을 알 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. 이름 축약&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;축약된 이름이 무엇인지 파악하기 위해서 주변의 로직을 읽고 유추하는 등의 시간이 들어가는 것은 낭비다.&amp;nbsp;&lt;b&gt;기본적으로 이름은 축약하지 않는다.&lt;/b&gt; 다만, 이름 생략을 완전히 금지해야 한다는 것은 아니고, 일반적으로도 축약한 이름이 통용된다면 괜찮다. (for 문에서 i, j등 카운터 변수, SNS, VIP 등 관습적으로 사용되는 축약한 형태 등)&lt;/p&gt;</description>
      <category>Study/내 코드가 그렇게 이상한가요?</category>
      <category>clean code</category>
      <category>java</category>
      <category>Refactoring</category>
      <category>내 코드가 그렇게 이상한가요?</category>
      <category>설계</category>
      <category>코딩</category>
      <category>클린코드</category>
      <author>aa_rong_blog</author>
      <guid isPermaLink="true">https://poalim.tistory.com/55</guid>
      <comments>https://poalim.tistory.com/55#entry55comment</comments>
      <pubDate>Sun, 11 Feb 2024 16:55:55 +0900</pubDate>
    </item>
    <item>
      <title>9. 설계의 건전성을 해치는 여러 악마</title>
      <link>https://poalim.tistory.com/54</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이번 장에서는 지금까지 소개한 것 이외의 나쁜 코드 및 대처 방안을 공부한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 데드 코드&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;절대 실행되지 않는 조건 내부에 있는 코드를&amp;nbsp;&lt;b&gt;데드 코드(dead code)&amp;nbsp;&lt;/b&gt;또는 &lt;b&gt;도달 불가능한 코드(unreachable code)&lt;/b&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;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;언젠가 버그가 될 가능성이 있다.
&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;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데드 코드는 발견되는 즉시 제거하는 것이 좋다. 버전 관리 시스템인 Git 등을 활용하여 변경 이력을 관리하면, 코드를 제거했을 때 발생할 수 있는 문제들을 따로 걱정하지 않아도 된다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. YAGNI 원칙&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;YAGNI&lt;/b&gt;라는 소프트웨어 원칙이 있다. &lt;b&gt;'You Aren't Gonna Need It.'&lt;/b&gt;의 약자로, &lt;b&gt;'지금 필요 없는 기능을 만들지 말라!'&lt;/b&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;3. 매직 넘버&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;매직 넘버란 설명이 없는 숫자&lt;/b&gt;를 말한다. 코드에 아무런 설명이 없이 특정한 숫자가 사용되면, 로직을 읽는 것만으로는 이 숫자의 의도를 알아내기 힘들고, 이러한 매직 넘버는 동일한 값이 여러 위치에 등장하여 중복 코드를 만들어낸다. 필요한 특정한 숫자가 있다면 의미를 알 수 있는 이름을 붙여서 상수를 만들어 활용하자.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 문자열 자료형에 대한 집착&lt;/h2&gt;
&lt;pre id=&quot;code_1707594013050&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 레이블 문자열, 표시 색(RGB), 최대 문자 수
String title = &quot;타이틀,255,250,240,64&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의미가 다른 여러 개의 값을 하나의 String 변수에 무리하게 넣으면 의미를 알기 어려울 뿐 아니라 split 메서드 등을 활용해야 하기 때문에 쓸데없이 로직이 복잡해진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;의미가 다른 값은 각각 다른 변수에 저장하는 것이 좋다.&lt;/b&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 전역 변수&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 곳에서 접근할 수 있는 변수를&amp;nbsp;&lt;b&gt;전역 변수&lt;/b&gt;라고 부른다. 자바 언어에서는 전역 변수 사양이 없지만, 변수를 public static으로 선언하면 모든 곳에서 접근할 수 있다.&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;b&gt;영향 범위가 가능한 한 되도록 좁게 설계해야 한다.&amp;nbsp;&lt;/b&gt;관계없는 로직에서는 접근할 수 없게 설계하여, 호출할 수 있는 위치가 적고 국소적일수록 로직을 이해하고 구현하기 쉽다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. null 문제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변수에 null이 들어갈 수 있도록 설계하면, 해당 변수를 사용하는 모든 곳에서 null 체크를 해야한다. 이는 가독성이 크게 떨어지게 만들고, 실수로 null 체크를 빠트린다면 곧바로 NullPointerException이 발생할 위험에 처한다. 따라서 null 체크 자체가 필요하지 않도록 &lt;b&gt;애초에 null을 다루지 않게 만들어야 한다.&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;null을 리턴하지 않는 설계 : 메서드에서 null을 리턴하지 않게 작성하는 것&lt;/li&gt;
&lt;li&gt;null을 전달하지 않는 설계 : 메서드에서 null을 변수에 할당하지 않는 것&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;null이란 '잘못된 처리'를 의미하여 무언가 갖고 있지 않거나 설정되지 않은 상태조차 존재하지 않음을 뜻한다.&amp;nbsp;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;무언가 존재하지 않는 상태 등을 표현하려면 null이 아닌 EMPTY 등의 상태를 만들어서 할당하자.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;코틀린 등은 기본적으로 모든 자료형에 null을 할당하지 못하는 null 안전 자료형을 사용한다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;7. 예외를 catch하고서 무시하는 코드&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;try-catch로 예외를 catch하고도 별다른 처리를 하지 않는 코드는 예외가 발생해도 무시하고 넘어가기 때문에, 오류가 발생해도 어느 시점에 어떤 코드에서 문제가 발생했는지 발견하기 어렵게 만든다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;이러한 상황을 피하려면, 잘못된 상태에 어떠한 관용도 베풀어서는 안 된다. 예외를 확인했다면 곧바로 통지, 기록하는 것이 좋고, 상황에 따라서는 바로 복구해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;8. 설계 질서를 파괴하는 메타 프로그래밍&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;프로그램 실행 중에 해당 프로그램 구조 자체를 제어하는 프로그래밍을 메타 프로그래밍이라고 부른다. 자바에서는 &lt;b&gt;리플렉션 API&lt;/b&gt;가 여기에 해당한다. 이를 통해 일반적인 프로그래밍에서는 접근할 수 없는 부분까지 접근할 수 있기 때문에, '흑마법'이라고 불리기도 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;리플렉션을 사용하면 final로 지정한 변수의 값도 바꿀 수 있고, private으로 외부에서 접근하지 못하게 만든 변수에도 접근할 수 있다. 따라서 리플렉션을 남용하면, 이 책에서 소개하고 있는 '잘못된 상태로부터 클래스를 보호하는 설계'와 '영향 범위를 최대한 좁게 만드는 설계'가 아무 의미도 없어진다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;메타 프로그래밍의 단점을 이해하지 못하고 사용하면, 유지 보수와 변경이 정말 힘들어진다. 메타 프로그래밍을 사용하고 싶다면 시스템 분석 용도로 한정하거나, 아주 작은 범위에서만 활용하는 등 리스크를 최소화해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;9. 기술 중심 패키징&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;패키지를 구분할 때 폴더를 적절하게 나누지 않으면 여러 문제가 생길 수 있다. 스프링(Spring) 등 여러 웹 프레임워크에서는 MVC 아키텍처를 채택하고 있다. 이와 같은 아키텍처에서는 기본적으로 폴더 구성을 models, views, controllers로 구분한다.(기술 중심 패키징)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;비즈니스 클래스들은 기술 중심 패키징에 따라 폴더를 구분하면, 관련성을 알기 매우 힘들어진다. 비즈니스 클래스는 아래와 같이 관련된 비즈니스 개념을 기준으로 폴더를 구분하는 것이 좋다.&lt;/span&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;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;재고_유스케이스.java&lt;/li&gt;
&lt;li&gt;발주_엔티티.java&lt;/li&gt;
&lt;li&gt;입고_엔티티.java&lt;/li&gt;
&lt;li&gt;출고_엔티티.java&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;주문
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;주문_유스케이스.java&lt;/li&gt;
&lt;li&gt;장바구니_엔티티.java&lt;/li&gt;
&lt;li&gt;주문_엔티티.java&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;결제
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;지불_유스케이스.java&lt;/li&gt;
&lt;li&gt;청구_엔티티.java&lt;/li&gt;
&lt;li&gt;청구_금액.java&lt;/li&gt;
&lt;li&gt;할인_포인트.java&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;10. 샘플 코드 복사해서 붙여넣기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그래밍 언어와 프레임워크에 저마다 있는 공식 사이트는, 사양과 문서와 더불어 다양한 샘플 코드가 함께 적혀 있다. 추가로, 기술 커뮤니티 사이트, Q&amp;amp;A 사이트, 엔지니어 개인 블로그 등에서도 설명과 함께 샘플 코드를 제공하낟.&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;11. 은 탄환&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서양에서 늑대 인간과 악마는 은으로 만들어진 총알(Silver bullet)로 죽일 수 있다고 알려져 있다. 그래서 어떤 문제를 해결하는 비장의 무기, 묘책을 '은 탄환'이라고 부른다. 하지만 &lt;b&gt;소프트웨어 개발에는 은 탄환이 없다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 문제가 있을 때, 어떤 방법이 해당 문제에 효과적인지, 비용이 더 들지는 않는지 평가하고 판단하는 자세가 무엇보다 중요하다. 문제와 목적을 머릿속에 새겨 두고, 적절한 기술을 선택할 수 있도록 노력하자.&lt;/p&gt;</description>
      <category>Study/내 코드가 그렇게 이상한가요?</category>
      <category>clean code</category>
      <category>java</category>
      <category>Refactoring</category>
      <category>내 코드가 그렇게 인상한가요?</category>
      <category>설계</category>
      <category>코딩</category>
      <category>클린코드</category>
      <author>aa_rong_blog</author>
      <guid isPermaLink="true">https://poalim.tistory.com/54</guid>
      <comments>https://poalim.tistory.com/54#entry54comment</comments>
      <pubDate>Sun, 11 Feb 2024 05:15:53 +0900</pubDate>
    </item>
    <item>
      <title>8. 강한 결합 : 복잡하게 얽혀서 풀 수 없는 구조</title>
      <link>https://poalim.tistory.com/53</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;결합도&lt;/b&gt;란 '모듈 사이의 의존도를 나타내는 지표'라고 할 수 있다. 어떤 클래스가 다른 클래스에 많이 의존하고 있는 구조를&amp;nbsp;&lt;b&gt;강한결합(tightlycoupling)&lt;/b&gt;이라고 부른다. 강한 결합 코드는 이해하기 힘들고, 변경하기도 굉장히 힘들다. 이 장에서는 강한 결합을 결합도가 낮은 구조인 &lt;b&gt;느슨한 결합(loose coupling)&lt;/b&gt;으로 개선하여 코드 변경과 이해가 쉬운 구조를 만드는 방법을 알아본다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 결합도와 책무&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소프트웨어가 출력, 금액 계산, 데이터베이스 등의 다양한 관심사를 가질 때, 만약 출력에 버그가 있는데 데이터베이스 로직을 수정하는 것은 말이 안 된다. 출력을 제대로 하는 것은 출력하는 로직의&amp;nbsp;&lt;b&gt;책임&lt;/b&gt;이라고 할 수 있다. &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;즉 소프트웨어의 책임이란&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;'자신의 관심사와 관련해서, 정상적으로 동작하도록 제어하는 것'&lt;/b&gt;이라고 볼 수 있다. 이때 중요한 것이 바로 단일 책임 원칙이다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;단일 책임 원칙&lt;br /&gt;&lt;/b&gt;모든 클래스는 하나의 책임만 가지며, 클래스는 그 책임을 완전히 캡슐화해야 한다. 클래스가 제공하는 모든 기능은 이 책임과 주의 깊게 부합해야 한다.&lt;/blockquote&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;pre id=&quot;code_1705413703498&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 정가 클래스
class RegularPrice {
    private static final int MIN_AMOUNT = 0;
    final int amount;
    
    RegularPrice(final int amount) {
        if (amount &amp;lt; MIN_AMOUNT) {
            thorw new IllegalArgumentException(&quot;가격은 0 이상이어야 합니다.&quot;);
        }
        this.amount = amount;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1705413797535&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 일반 할인 클래스
class RegularDiscountedPrice {
    private static final int MIN_AMOUNT = 0;
    private static final int DISCOUNT_AMOUNT = 4000;
    final int amount;
    
    RegularDiscountedPrice(final RegularPrice price) {
        int discountedAmount = price.amount - DISCOUNT_AMOUNT;
        if (discountedAmount &amp;lt; MIN_AMOUNT) {
            discountedAmount = MIN_AMOUNT;
        }
        amount = discountedAmount;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1705413910825&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 여름 할인 클래스
class SummerDiscountedPrice {
    private static final int MIN_AMOUNT = 0;
    private static final int DISCOUNT_AMOUNT = 3000;
    final int amount;
    
    SummerDiscountedPrice(final RegularPrice price) {
        int discountedAmount = price.amount - DISCOUNT_AMOUNT;
        if (discountedAmount &amp;lt; MIN_AMOUNT) {
            discountedAmount = MIN_AMOUNT;
        }
        amount = discountedAmount;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반 할인 가격, 여름 할인 가격이 모두 할인 가격을 나타내는 클래스라고 하여도 각각은 다른 관심사를 가지고 있으므로 클래스를 분리하여 구분하였다. 이와 같이 관심사에 따라 분리해서 독립되어 있는 구조를&amp;nbsp;&lt;b&gt;느슨한 결합&lt;/b&gt;이라고 부르고, 이러한 구조는 할인과 관련된 사양이 변경되어도 서로 영향을 주지 않는다. &lt;b&gt;일반적으로&lt;/b&gt; &lt;b&gt;느슨한 결합의 설계를 권한다.&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;※ 잘못된 예(강한 결합)&lt;/b&gt;&lt;br /&gt;일반 할인 클래스를 만든 후, 일반 할인 클래스를 참조하여 추가 로직을 더함으로써 여름 할인 클래스를 설계하는 경우. 이런 경우 일반 할인 클래스의 변경이 여름 할인 클래스에 영향을 줄 수 있다.&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;DRY 원칙의 잘못된 적용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드에서 RegularDiscountedPrice와 SummerDiscountedPrice의 로직은 대부분 같다. 이를 보고 '중복 코드가 작성된 것은 아닐까?'라고 생각할 수 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;DRY 원칙(Don't Repeat Yourself) : 반복을 피해라!&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 원칙을 소개한 《실용주의 프로그래머 20주년 기념판》에서는 아래와 같이 설명한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;모든 지식은 시스템 내에서 단 한 번만, 애매하지 않고, 권위 있게 표현되어야 한다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 '지식'을 소프트웨어가 대상으로 하는 '비즈니스 지식'의 관점에서 생가갷보면, 일반 할인과 여름 할인은 서로 다른 개념이다. DRY는 각각의 개념 단위 내에서 반복을 하지 말라는 의미이다. &lt;b&gt;같은 로직, 비슷한 로직이라도 개념이 다르면 중복을 허용해야 한다.&amp;nbsp;&lt;/b&gt;개념적으로 다른 것까지도 무리하게 중복을 제거하려고 하면, 단일 책임 원칙을 깨고 강한 결합 상태가 된다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 다양한 강한 결합 사례와 대처 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;강한 결합은 다양한 원인으로 발생한다. 다양한 강한 결합 사례와 대처 방법을 알아보자.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2.1 상속과 관련된 강한 결합&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;상속은 주의해서 다루지 않으면, 곧바로 강한 결합 구조를 유발하는 위험한 문법이다. &lt;/b&gt;(이 책에서는 상속 자체를 권장하지 않는다.) 예를 들어 살펴보자.&lt;/p&gt;
&lt;pre id=&quot;code_1705415415405&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 물리 공격 클래스
class PhysicalAttack {
    // 단일 공격 대미지 리턴
    int singleAttackDamage() { ... }
    
    // 2회 공격 대미지 리턴
    int doubleAttackDamage() { ... }
}

// 격투가는 물리 공격 시 추가 대미지 부여(단일 공격 20, 2회 공격 10)
class FighterPhysicalAttack extends PhysicalAttack {
    @Override
    int singleAttackDamage() {
        return super.singleAttackDamage() + 20;
    }
    
    @Override
    int doubleAttackDamage() {
        return super.doubleAttackDamage() + 10;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드에서 FighterPhysicalAttack은 PhysicalAttack 클래스를 상속한다. 이러한 구조에서, 어느날 PhysicalAttack 클래스의 doubleAttackDamage 메서드의 구현이 개별 구현에서 singleAttackDamage()를 두 번 호출하도록 변경되었다고 하자. 이 때, 격투가의 doubleAttackDamage()는 10의 추가 피해가 아니라, 50(20+20+10)의 추가 피해를 입히게 되어 버그가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 &lt;b&gt;상속 관계에서 서브 클래스는 슈퍼 클래스에 굉장히 크게 의존한다.&lt;/b&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;을 사용하는 것이 좋다. 컴포지션이란 사용하고 싶은 클래스를 아래 코드처럼 private 인스턴스 변수로 갖고 사용하는 것을 의미한다.&lt;/p&gt;
&lt;pre id=&quot;code_1705415784483&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 격투가 물리 공격 클래스(컴포지션 사용)
class FighterPhysicalAttack {
    private final PhysicalAttack physicalAttack;
    
    ...
    
    int singleAttackDamage() {
        return physicalAttack.singleAttackDamage() + 20;
    }
    
    int doubleAttackDamage() {
        return physicalAttack.doubleAttackDamage() + 10;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 컴포지션 구조를 사용하면, PhysicalAttack의 로직을 변경해도 FighterPhysicalAttack이 영향을 적게 받는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 상속은 반드시 단일 책임 원칙을 염두에 두고 구현하여야 한다. 그리고 값 객체와 컴포지션 등 다른 설계를 사용할 수는 없는지 반드시 검토해야 한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2.2 인스턴스 변수별로 클래스 분할이 가능한 로직&lt;/h3&gt;
&lt;pre id=&quot;code_1705416303210&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 온라인 쇼핑몰에서 사용되고 있는 가상의 코드
class Util {
    private int reservationId;		// 상품 예약 ID
    private ViewSettings viewSettings;	// 화면 표시 설정
    private MailMagazine mailMagazine;	// 메일 매거진
    
    void cancelReservation() { ... }	// reservationId 사용
    
    void darkMode() { ... }	// viewSettings 사용
    
    void beginSendMail() { ... }	// mailMagazine 사용
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cancelReservation(예약 취소), darkMode(다크 모드로 전환), beginSendMail(메일 보내기)은 모두 책임이 완전히 다른 메서드이지만, 모두 Util이라는 클래스 하나에 정의되어 있다. 책임이 다른 메서드가 한 클래스 안에 정의되어 있으면, 여러 문제가 발생할 수 있다. 즉, 위와 같은 Util 클래스는 아래와 같은 3개의 클래스로 분리하면, 강한 결합 문제가 사라진다.&lt;/p&gt;
&lt;pre id=&quot;code_1705416647998&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 예약 클래스
class Reservation {
    private final int reservatinoId;
    ...
    void cancel() { ... }
}

// 화면 모드 관련 클래스
class ViewCustomizing {
    private final ViewSettings viewSettings;
    ...
    void darkMode() { ... }
}

// 메일 매거진 서비스 클래스
class MailMagazineService {
    private final MailMagazine mailMagazine;
    ...
    void beginSend() { ... }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의존 관계가 단순했던 위의 Util 클래스와 다르게 클래스 간 의존 관계가 훨씬 복잡한 코드에서 클래스를 잘 분리하려면, &lt;b&gt;각각의 인스턴스 변수와 메서드가 무엇과 관련이 있는 지 잘 파악해야 한다.&amp;nbsp;&lt;/b&gt;관계를 파악할 때는 의존 관계를 그림으로 그려보면 도움이 된다.(영향 스케치)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2.3 특별한 이유 없이 public 사용하지 않기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특별한 이유 없이 붙이는 public은 강한 결합 구조를 만들 수 있습니다. 이러한 경우, 관계를 맺지 않았으면 하는 클래스끼리도 결합되어 영향 범위가 확대될 수 있고, 결과적으로 유지 보수가 어려운 강한 결합 구조가 된다. 강한 결합을 피하려면, 접근 수식자로 필요한 범위의 가시성을 적절하게 제어해야 한다. 자바의 접근 수식자는 아래와 같다.&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;public&lt;/b&gt; : 모든 클래스에서 접근 가능&lt;/li&gt;
&lt;li&gt;&lt;b&gt;protected&lt;/b&gt; : 같은 클래스와 서브 클래스에서 접근 가능&lt;/li&gt;
&lt;li&gt;&lt;b&gt;없음&lt;/b&gt; : 같은 패키지에서만 접근 가능, package private이라고 부름.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;private&lt;/b&gt; : 같은 클래스에서만 접근 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 접근 수식자를 생략한 디폴트 상태(기본 값)은 package private일까? 이는 패키지들의 불필요한 의존 관계를 피할 때 package private이 적절하기 때문이다. 패키지는 밀접한 클래스끼리 응집하게 설계하고, 패키지 바깥의 클래스와는 느슨하게 결합하도록 설계한다. 즉, 외부에서는 접근할 수 없게 하기 위해 package private이 적절한 것이다. 외부에 정말로 공개하고 싶은 클래스에만 한정해서 public을 붙인다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2.4 private 메서드가 너무 많다는 것은 책임이 너무 많다는 것&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;private 메서드가 너무 많이 쓰인 클래스는 하나가 아닌 너무 많은 책임을 갖고 있을 가능성이 높다. 책임이 다른 메서드는 다른 클래스로 분리하는 것이 좋다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2.5 높은 응집도를 오해해서 생기는 강한 결합&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관련이 깊은 데이터와 로직을 한 곳에 모은 구조를 응집도가 높은 구조라고 한다. &lt;b&gt;응집도가 높다는 개념을 염두에 두고, 관련이 깊다고 생각되는 로직을 한곳에 모으려고 했지만, 결과적으로 강한 결합 구조를 만드는 상황은 매우 자주 일어난다.&amp;nbsp;&lt;/b&gt;각각의 개념을 분리해야, 느슨한 결합 구조로 만들 수 있다. 즉, &lt;b&gt;'결합이 느슨하고 응집도가 높은 설계'&lt;/b&gt;가 한 덩어리가 되어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;응집도를 높이는 설계를 할 때는 다른 개념이 섞여 들어와 강한 결합을 만드는게 아닌지 항상 주의해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이외에도 아래와 같은 강한 결합을 만드는 사례들이 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;스마트 UI&lt;/b&gt; : 화면 표시에 관한 책무와 그렇지 않은 책무가 강하게 결합되어 있기 때문에 변경하기가 매우 힘들다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;거대 데이터 클래스&lt;/b&gt; : 거대 데이터 클래스는 다양한 데이터를 가지므로 수많은 유스케이스에서 사용된다. 결국, 전역 변수와 같은 성질을 띠게 되며, 여러 폐해를 불러 일으킨다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;트랜잭션 스크립트 패턴&lt;/b&gt; : 메서드 내부에 일련의 처리가 하나하나 길게 작성되어 있는 구조로, 남용하게 되면 응집도는 낮아지고 결합은 강해진다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;갓 클래스&lt;/b&gt; : 트랜잭션 스크립트 패턴에서 한 단계 나아간 단계이다. 하나의 클래스 내부에서 수천에서 수만 줄의 로직을 담고 있으며, 수많은 책임을 담당하는 로직이 난잡하게 섞여 있는 클래스이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;위에서 설명한 수많은 강한 결합 클래스에 대처하는 방법은 모두 같다. 바로, 객체 지향 설계와 단일 책임 원칙에 따라 제대로 설계하는 것이다.&lt;/b&gt;&lt;/p&gt;</description>
      <category>Study/내 코드가 그렇게 이상한가요?</category>
      <category>CleanCode</category>
      <category>java</category>
      <category>Refactoring</category>
      <category>내 코드가 그렇게 이상한가요?</category>
      <category>설계</category>
      <category>코딩</category>
      <category>클린코드</category>
      <author>aa_rong_blog</author>
      <guid isPermaLink="true">https://poalim.tistory.com/53</guid>
      <comments>https://poalim.tistory.com/53#entry53comment</comments>
      <pubDate>Wed, 17 Jan 2024 00:44:41 +0900</pubDate>
    </item>
    <item>
      <title>7. 컬렉션 : 중첩을 제거하는 구조화 테크닉</title>
      <link>https://poalim.tistory.com/52</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이 장에서는 배열과 List와 같은 컬렉션에서 발생할 수 있는 다양한 문제 사례들을 알아본다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 이미 존재하는 기능을 다시 구현하지 말기&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;바퀴의 재발명&lt;/b&gt;&lt;br /&gt;이미 널리 사용되고 있는 기술과 해법이 존재하는데도, 이를 &lt;u&gt;전혀 모르거나&lt;/u&gt; 의도적으로 무시하고 비슷한 것을 새로 만들어 내는 것&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대부분의 언어에서는 표준 컬렉션 라이브러리에 많은 기능이 구현되어 있다. 반복문을 사용해서 컬렉션을 직접 조작하고 있다면, 잠시 멈추고 표준 라이브러리에 같은 기능을 하는 메서드가 있는지 확인해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1705402291967&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 반복문을 사용하여 List를 직접 조작하는 예시
boolean hasPrisonKey = false;
for (Item each : items) {
    if (each.name.equals(&quot;prisonKey&quot;)) {
        hasPrisonKey = true;
        break;
    }
}

// 자바 표준 컬렉션 라이브러리 메서드 사용(anyMatch)
boolean hasPrisonKey = items.stream().anyMatch(
    item -&amp;gt; item.name.equals(&quot;prisonKey&quot;);
)&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 반복문 내부 조건 분기 중첩 해결하기&lt;/h2&gt;
&lt;pre id=&quot;code_1705402659712&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 멤버의 상태가 중독인 경우 hitPoint를 감소시키는 로직
for (Member member : members) {
    if (0 &amp;lt; member.hitPoint) {
        if (member.containState(StateType.poison)) {
            member.hitPoint -= 10;
            if (member.hitPoint &amp;lt;= 0) {
                member.hitPoint = 0;
                member.addState(StateType.dead);
                member.removeState(StateType.poison);
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 반복문 내에서 중첩된 조건 분기는 가독성이 좋지 않다. 이러한 조건 분기 중첩을 해결하는 방법을 살펴보자.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2.1 조기 continue로 제거하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;continue는 실행하고 있는 처리를 건너 뛰고, 다음 반복으로 넘어가는 제어 구문이다.&lt;/p&gt;
&lt;pre id=&quot;code_1705407526956&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 조기 continue로 조건 분기 중첩이 제거된 코드
for (Member member : members) {
    if (member.hitPoint == 0) continue;	// 살아있지 않다면 건너뛰기
    if (!member.containsState(StateType.poision)) continue;	// 중독 상태가 아니라면 건너뛰기
    
    member.hitPoint -= 10;
    
    if (0 &amp;lt; member.hitPoint) continue;
    member.hitPoint = 0;
    member.addState(StateType.dead);
    member.removeState(StateType.poison);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만, 조기 continue를 사용할 때는 기존 조건을 반전시켜 주어야 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기존 조건 : hitPoint가 0보다 크다면 조건 분기 실행&lt;/li&gt;
&lt;li&gt;변경 조건 : hitPoint가 0보다 같거나 작다면 continue&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2.2 조기 break로 제거하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;break는 처리를 중단하고, 반복문 전체를 벗어나는 제어 구문이다. 반복문의 전체 반복이 하나의 조건에 영향을 받는다면, 해당 조건이 성사되지 않은 경우 break로 전체 반복문을 벗어나게 만들 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 응집도가 낮은 컬렉션 처리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컬렉션 처리에서도 역시 하나의 컬렉션 처리를 여러 곳에서 구현하는 등 응집도가 낮은 코드가 발생할 수 있다. 이를 해결할 수 있는 방법을 알아보자.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3.1 컬렉션 처리를 캡슐화하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;일급 컬렉션(퍼스트 클래스 컬렉션, First Class Collection) 패턴&lt;/b&gt;을 사용하여 컬렉션과 관련된 응집도가 낮아지는 문제를 해결할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;일급 컬렉션 패턴 &lt;/b&gt;: 컬렉션과 관련된 로직을 캡슐화하는 디자인 패턴&lt;/blockquote&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;pre id=&quot;code_1705408691160&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Party {
    static final int MAX_MEMBER_COUNT = 4;
    private final List&amp;lt;Member&amp;gt; members;	// 컬렉션 자료형의 인스턴스 변수
    
    Party() {
        members = new ArrayList&amp;lt;Member&amp;gt;();
    }
    
    private Party(List&amp;lt;Member&amp;gt; members) {
        this.members = members;
    }
    
    
    // 컬렉션 자료형의 인스턴스 변수 members와 관련된 로직을 Party 클래스로 모은다.
    
    /**
    * 멤버 추가하기
    */
    Party add(final Member newMember) {
        if (exists(newMember)) {
            throw new RuntimeException(&quot;이미 파티에 참가되어 있습니다.&quot;);
        }
        if (isFull()) {
            throw new RuntimeException(&quot;이 이상 멤버를 추가할 수 없습니다.&quot;);
        }
        
        // 인스턴스 변수를 직접 변경하는 것에서 오는 부수효과를 막기 위해 새로운 리스트를 생성하여 리턴한다.
        final List&amp;lt;Member&amp;gt; adding = new ArrayList&amp;lt;&amp;gt;(members);
        adding.add(newMember);
        return new Party(adding);
    }
    
    boolean isAlive() { ... }
    
    boolean exists(final Member member) { ... }
    
    boolean isFull() { ... }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 컬렉션을 사용하는 설계에서도 응집도가 높은 구성을 만들어야 한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3.2 외부로 전달할 때 컬렉션의 변경 막기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 3.1에서와 같이 Party 클래스를 응집도가 높게 설계를 했더라도, 외부에 members 인스턴스 변수를 그대로 전달하면 Party 클래스 외부에서 마음대로 멤버를 추가하고 제거해 버릴 수 있다. 이러한 사용은 응집도가 낮을 때와 아무 차이가 없게 만든다.&lt;/p&gt;
&lt;pre id=&quot;code_1705408961259&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Party 클래스 외부에서 members 리스트를 마음대로 조작하는 상황
members = party.members();
members.add(newMember);
...
members.clear();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 상황을 막고 진정한 의미의 응집도가 높은 코드를 만들기 위해서, &lt;b&gt;외부로 전달할 때는 컬렉션의 요소를 변경하지 못하게 막아 두는 것이 좋다. 이 때 unmodifiableList 메서드를 사용한다.&amp;nbsp;&lt;/b&gt;(기존에 인스턴스 변수에 final 키워드를 사용하던 것과 같은 맥락이다.)&lt;/p&gt;
&lt;pre id=&quot;code_1705409134328&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 외부에서 변경하지 못하도록 불변 리스트로 만들어서 전달하기
class Party {
    ...
    List&amp;lt;Member&amp;gt; members() {
        return members.unmodifiableList();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;unmodifiableList로 리턴되는 컬렉션은 요소를 추가하거나 제거할 수 없다. 따라서 Party 클래스 외부에서 마음대로 컬렉션을 조작하는 상황 자체를 방지할 수 있다.&lt;/p&gt;</description>
      <category>Study/내 코드가 그렇게 이상한가요?</category>
      <category>CleanCode</category>
      <category>java</category>
      <category>Refactoring</category>
      <category>내 코드가 그렇게 이상한가요?</category>
      <category>설계</category>
      <category>코딩</category>
      <category>클린코드</category>
      <author>aa_rong_blog</author>
      <guid isPermaLink="true">https://poalim.tistory.com/52</guid>
      <comments>https://poalim.tistory.com/52#entry52comment</comments>
      <pubDate>Tue, 16 Jan 2024 21:48:02 +0900</pubDate>
    </item>
    <item>
      <title>6. 조건 분기 : interface 활용하여 복잡한 조건 분기 제거하기</title>
      <link>https://poalim.tistory.com/51</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;조건 분기는 조건에 따라 처리 내용을 전환하는 꼭 필요한 구조이지만, 조건이 복잡해지면 코드의 동작을 이해하기 힘들고 유지보수 시 여러 버그를 만들 수 있다. 이 장에서는 조건 분기를 잘 다루어 버그가 없는 설계를 만드는 방법을 설명한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 중첩되는 조건 분기 제거하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 보다보면 if 조건문이 여러 개 중첩되어 사용된 경우가 있다. &lt;b&gt;조건 분기의 중첩은 코드의 가독성을 크게 떨어뜨린다.&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1704528666392&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (조건) {
    // ...
    if (조건) {
        // ...
        if (조건) {
            // ...
            if (조건) {
                // ...
            }
            else {
                // ...
            }
        }
    }
}&lt;/code&gt;&lt;/pre&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;조기 리턴(early return)&lt;/b&gt;이 있다. 아래 예제를 보자.&lt;/p&gt;
&lt;pre id=&quot;code_1704528971755&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 살아 있는가
if (0 &amp;lt; member.hitPoint) {
    // 움직일 수 있는가
    if (member.canAct()) {
    	// 매직 포인트가 남아 있는가
        if (magic.costMagicPoint &amp;lt;= member.magicPoint) {
            member.consumeMagicPoint(magic.costMagicPoint); // 매직 포인트 소비
            member.chant(magic); // 마법 발동
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 멤버가 스킬 사용 시 여러 조건을 체크한 후 매직 포인트를 소비하고 마법을 발동하는 코드이다. 중첩된 조건 분기들을 조기 리턴으로 제거해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1704529126868&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 살아 있지 않은 경우 처리를 종료
if (member.hitPoint &amp;lt;= 0) return;
// 움직일 수 없다면 처리를 종료
if (!member.canAct()) return;
// 매직 포인트가 부족하다면 처리를 종료
if (member.magicPoint &amp;lt; magic.costMagicPoint) return;

member.consumeMagicPoint(magic.costMagicPoint);
member.chant(magic);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;조기 리턴으로 중첩이 제거되어 가독성이 좋아졌다.&lt;span&gt; 위에 예제에서는 &lt;/span&gt;&lt;/span&gt;조기 리턴하는 형태로 변경하기 위해 원래 조건을 반전하였다.('살아 있는가?' -&amp;gt; '죽은 상태인가?')&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조기 리턴에는 또 다른 장점이 있다. 바로 &lt;b&gt;조건 로직과 실행 로직을 분리할 수 있다&lt;/b&gt;는 것이다. 조건 로직과 실행 로직이 복잡하게 섞여있던 이전 코드에 비해서, 조기 리턴을 사용한 코드는 마법을 쓸 수 없는 조건은 앞부분에 조기 리턴으로 모았고, 마법 발동 때 실행할 로직은 뒤로 모았다. 이러한 구조는 새로운 조건과 실행 로직을 추가할 때 더 빛을 발한다.&lt;/p&gt;
&lt;pre id=&quot;code_1704529470719&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (member.hitPoint &amp;lt;= 0) return;
if (!member.canAct()) return;
if (member.magicPoint &amp;lt; magic.costMagicPoint) return;
if (!(발동 조건1)) return;
if (!(발동 조건2)) return;

member.consumeMagicPoint(magic.costMagicPoint);
member.chant(magic);
// 실행 로직 추가&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;else를 사용하는 복잡한 조건 분기도&amp;nbsp;&lt;b&gt;조기 리턴&lt;/b&gt;을 사용하여 개선이 가능하다.&lt;/p&gt;
&lt;pre id=&quot;code_1704529794717&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (hitPointRate == 0) {
    return HealthCondition.dead;
}
else if (hitPointRate &amp;lt; 0.3) {
    return HealthCondition.danger;
}
else if (hitPointRate &amp;lt; 0.5) {
    return HealthCondition.caution;
}
else {
    return HealthCondition.fine;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드는 아래와 같이 개선이 가능하다.&lt;/p&gt;
&lt;pre id=&quot;code_1704529899863&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (hitPointRate == 0) return HealthCondition.dead;
if (hitPointRate &amp;lt; 0.3) return HealthCondition.danger;
if (hitPointRate &amp;lt; 0.5) return HealthCondition.caution;

return HealthCondition.fine;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. switch 조건문 중복 제거하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;값의 종류에 따라 다르게 처리하고 싶을 때 많이 사용되는 &lt;b&gt;switch 조건문은 버그를 만들기 굉장히 쉬운 제어 구문&lt;/b&gt;이다. RPG 게임에서 효과가 다른 여러 마법을 구현한다고 생각해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1704531207511&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;enum MagicType {
    fire,	// 불 계열 마법
    lightning	// 번개 계열 마법
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 마법에 이름, 매직 포인트 소비량, 공격력의 요구 사항이 필요할 때 각 메서드를 구현하면 아래와 같이 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1704531386831&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 마법의 이름
String getName(MagicType magicType) {
    String name = &quot;&quot;;
    
    switch (magicType) {
    	case fire:
        	name = &quot;파이어&quot;;
            break;
        case lightning:
        	name = &quot;라이트닝&quot;;
            break;
    }
    return name;
}

// 매직포인트 소비량
int costMagicPoint(MagicTYpe magicType, Member member) {
    int magicPoint = 0;
    
    switch (magicType) {
    	case fire:
            magicPoint = 2;
            break;
        case lightning:
            magicPoint = 5 + (int)(member.level * 0.2);
            break;
    }
    return magicPoint;
}

// 공격력
int attackPower(MagicType magicType, Member member) {
    int attackPower = 0;
    
    switch (magicType) {
    	case fire:
            attackPower = 20 + (int)(member.level * 0.5);
            break;
        case lightning:
            attackPower = 50 + (int)(member.agility * 1.5);
            break;
    }
    return attackPower;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 MagicType에 따른 처리를 switch 조건문으로 구현하는 것은 매우 좋지 않다. 먼저 &lt;b&gt;요구 사항의 변경 시 수정이 누락될 수 있다.&lt;/b&gt; 현재 구조는 fire, lightning 외에 새로운 마법을 추가해야 될 때, getName, costMagicPoint, attackPower 모든 메서드의 switch 조건문에 case 구문이 추가가 되어야 한다. 이러한 요구 사항의 추가, 수정, 삭제가 계속 반복되다 보면 어느 한 군데에서 수정이 누락이 발생하기 쉽고, 이는 곧 버그로 이어진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재는 2가지 마법(fire, lightning)에 3가지 요구사항(이름, 매직포인트 소비량, 공격력)이지만, 실제 RPG 게임에서는 수십~수백 가지의 마법 종류가 있고, 마찬가지로 수십개가 넘는 요구사항이 있다. 이런 상황에서 폭발적으로 늘어나는 switch 조건문에서는 아무리 주의 깊게 대응한다고 해도 누락을 피하기 쉽지 않다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;조건 분기 모으기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 switch 조건문 중복을 해소하려면,&amp;nbsp;&lt;b&gt;단일 책임 선택의 원칙&lt;/b&gt;을 생각해 봐야 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단일 책임 선택의 원칙 : 소프트웨어 시스템이 선택지를 제공해야 한다면, 그 시스템 내부의 어떤 한 모듈만으로 모든 선택지를 파악할 수 있어야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조건식이 같은 switch 조건문을 여러 번 작성한 우리의 코드는 이 원칙을 위반하므로, MagicType의 switch 조건문을 하나로 묶어보자.&lt;/p&gt;
&lt;pre id=&quot;code_1704532747064&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Magic {
    final String name;
    final int costMagicPoint;
    final int attackPower;
    final int costTechnicalPoint;
    
    Magic(final MagicType magicType, final Member member) {
        switch (magicType) {
            case fire:
                name = &quot;파이어&quot;;
                costMagicPoint = 2;
                attackPower = 20 + (int)(member.level * 0.5);
                break;
            case lightning:
                name = &quot;라이트닝&quot;;
                costMagicPoint = 5 + (int)(member.level * 0.2);
                attackPower = 50 + (int)(member.agility * 1.5);
                break;
            case hellFire:
                ...
            default:
                throw new IllegalArgumentException();
        }
    }
}​&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;switch 조건문 하나로 모든 요구 사항을 다루고 있으므로 사양을 변경할 때 누락 실수를 줄일 수 있다. 하지만 여전히 마법의 종류가 많아지면 클래스가 거대해지고 이에 따라 가독성이 떨어지게 된다. 따라서,&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;h2 data-ke-size=&quot;size26&quot;&gt;3. 인터페이스 활용하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터페이스를 잘 사용하면 조건 분기를 크게 줄일 수 있다. 즉 코드를 단순하게 만들 수 있고, &lt;b&gt;인터페이스를 잘 사용하는 지가 곧 설계 능력의 전환점이라고 할 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터페이스는 자바와 같은 객체 지향 프로그래밍 언어 특유의 문법으로, 기능 변경을 편리하게 할 수 있는 개념이다. &lt;b&gt;인터페이스를 사용하면, 분기 로직을 작성하지 않고도 분기와 같은 기능을 구현할 수 있다.&lt;/b&gt; 여러 도형의 면적을 구하는 예를 들어보자.&lt;/p&gt;
&lt;pre id=&quot;code_1704533353213&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 사각형
class Rectangle {
    private final double width;
    private final double height;
    
    double area() {
        return width * height;
    }
}

// 원
class Circle {
    private final double radius;
    
    double area() {
        return radius * radius * Math.PI;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;면적을 구하는 메서드가 area로 같은 이름이고 호출 역시 똑같은 방식으로 할 수 있을 것 같지만, Rectangle과 Circle은 클래스가 서로 다르다. 즉, 우리는 도형의 넓이를 구하기 위해서 각 도형이 어떤 도형인지(어느 클래스에 속하는지)를 먼저 알아야 한다. 이러한 상황을 구현하면 아래와 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1704533661890&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;double getArea(Object shape) {
    if (shape instanceof Rectangle) {
        return ((Rectangle) shape).area();
    }
    if (shape instanceof Circle) {
        return ((Circle) shape).area();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 도형의 자료형을 판정하고, 각 자료형으로 변환하는 등의 번거로운 작업을 해결해 주는 것이 인터페이스이다. &lt;b&gt;인터페이스는 서로 다른 자료형을 같은 자료형처럼 사용할 수 있게 해준다.&lt;/b&gt; 인터페이스를 사용해서 위 코드를 개선해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1704533845010&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface Shape {
    double area();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 도형을 나타내는 Shape라는 이름의 인터페이스를 만든 후, 면적을 구하는 area 메서드를 정의했다. 그리고, 각 도형 Rectangle과 Circle은 Shape 인터페이스를 구현하도록 변경한다.&lt;/p&gt;
&lt;pre id=&quot;code_1704533980266&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Rectangle implements Shape {
    private final double width;
    private final double height;
    
    public double area() {
        return width * height;
    }
}

class Circle implements Shape {
    private final double radius;
    
    public double area() {
        return radius * radius * Math.PI;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면, Rectangle과 Circle을 Shape라는 자료형 하나로 다룰 수 있게 되고 우리는 공통 메서드인 area를 사용할 때 각 도형이 어떤 타입인 지 알 필요가 없게 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1704534056194&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;double getArea(Shape shape) {
    return shape.area();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 메서드는 Shape 인터페이스를 구현하는 모든 클래스를 매개변수로 받을 수 있고, instanceof로 자료형을 판정하는 조건 분기를 작성하지 않아도 각 도형에 맞는 area 메서드가 실행된다. 또한, 새로운 도형(Triangle 등)을 추가할 때도, Shape를 구현하는 Triangle 클래스를 추가하기만 하면 이외의 사용 로직은 수정할 필요가 없다.&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;switch 조건문 -&amp;gt; 인터페이스 (전략 패턴)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 나왔던 switch 조건문 중복 문제를 인터페이스를 사용하여 해결해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터페이스의 큰 장점 중 하나는 다른 로직을 같은 방식으로 처리할 수 있다는 것이다. 앞서 살펴본 switch 조건문은 마법의 이름, 매직포인트 소비량, 공격력을 각 마법별로 다른 로직으로 처리했다. 이처럼 &lt;b&gt;공통으로 필요하지만 종류별로 다르게 처리해야 하는 기능을 인터페이스의 메서드로 정의한다.&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1704537418818&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface Magic {
    String name();
    int costMagicPoint();
    int attackPower();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;Tip. 인터페이스의 이름을 결정하는 방법 : 인터페이스를 구현하는 클래스들이 어떤 부류인가?(사각형, 원 -&amp;gt; 도형. 파이어, 라이트닝, 헬파이어 -&amp;gt; 마법)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 각 마법 클래스를 만들어서 Magic 인터페이스를 구현한다.&lt;/p&gt;
&lt;pre id=&quot;code_1704537739982&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Fire implements Magic {
    private final Member member;
    
    Fire(final Member member) {
        this.member = member;
    }
    
    public String name() {
        return &quot;파이어&quot;;
    }
    
    public int costMagicPoint() {
        return 2;
    }
    
    public int attackPower() {
        return 20 + (int)(member.level * 0.5);
    }
}

class Lightning implements Magic {...}

class HellFire implements Magic {...}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이와 같이 구현하면, Fire, Lightning HellFire를 모두 Magic 자료형으로 활용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 마법을 Magic 자료형으로 사용할 수 있게 되었지만, 아직 MagicType이 어떠한 Magic 자료형을 사용하여야 하는 지에 대한 처리가 필요하다. 이는 Map을 사용하여 처리할 수 있다. Map이 switch 조건문처럼 경우에 따라 처리를 구분하는 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1704537966449&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;final Map&amp;lt;MagicType, Magic&amp;gt; magics = new HashMap&amp;lt;&amp;gt;();

final Fire fire = new Fire(member);
final Lightning lightning = new Lightning(member);
final HellFire hellFire = new HellFire(member);

magics.put(MagicType.fire, fire);
magics.put(MagicType.lightning, lightning);
magics.put(MagicType.hellFire, hellFire);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 형식으로 MagicType과 Magic이 매핑되는 Map을 만들어두고, magicType을 매개변수로 전달받았을 때 magicType에 해당하는 Magic 인스턴스를 추출하여 로직을 처리하면 된다. 완성된 코드는 아래와 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1704538786947&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;final Map&amp;lt;MagicType, Magic&amp;gt; magics = new HashMap&amp;lt;&amp;gt;();

...

// 마법 공격 실행하기
void magicAttack(final MagicType magicType) {
    final Magic usingMagic = magics.get(magicType)	// magicType에 해당하는 Magic 인스턴스 추출
    
    showMagicName(usingMagic);
    consumeMagicPoint(usingMagic);
    magicDamage(usingMagic);
}

void showMagicName(final Magic magic) {
    System.out.println(magic.name());
}

void consumeMagicPoint(final Magic magic) {
    final int costMagicPoint = magic.costMagicPoint();
    // ...
}

void magicDamage(final Magic magic) {
    final int attackPower = magic.attackPower();
    // ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 인터페이스를 사용해서 처리를 한꺼번에 전환하는 설계를 &lt;b&gt;'전략 패턴(strategy pattern)'&lt;/b&gt;이라고 한다. 인터페이스를 활용한 전략 패턴은 버그의 발생을 미연에 방지할 수 있다. 왜냐하면, &lt;b&gt;인터페이스를 구현한 클래스는 인터페이스의 메서드를 모두 구현하여야 컴파일할 수 있기 때문이다.&lt;/b&gt; 만약에 MagicType에 HellFire라는 새로운 타입을 추가할 때, Magic 인터페이스를 구현하도록 HellFire 클래스를 만들었다면, Magic 인터페이스에 있는 name, costMaigcPoint, attackPower 메서드 중 하나라도 구현을 빠뜨린다면 컴파일 오류가 발생하고, 구현을 누락하는 실수를 방지할 수 있다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;플래그 매개변수&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래의 damageFlag와 같이 메서드의 기능을 전환하는 boolean 자료형의 매개변수를 플래그 매개변수라고 한다. 이러한 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;플래그 매개변수 또한, 전략 패턴을 사용하여 제거가 가능하다.&lt;span&gt; (하나의 메서드는 하나의 기능만 하는 것이 좋기 때문에 플래그 매개 변수를 사용하는 로직은 좋지 않다.)&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1704541673074&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void damage(boolean damageFlag, int damageAmount) {
    if (damageFlag == true) {
        // 물리 대미지 처리
        ...
    }
    else {
        // 마법 대미지 처리
        ...
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 전략패턴을 사용하여 변경해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1704541907771&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 대미지를 나타내는 인터페이스
interface Damage {
    void execute(final int damageAmount);
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1704542017321&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 물리 대미지
class HitPointDamage implements Damage {
    public void execute(final int damageAmount) {
        ...
    }
}

// 마법 대미지
class MagicPointDamage implements Damage {
    public void execute(final int damageAmount) {
        ...
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 Damage 인터페이스를 각각 구현하는 형태로 구성을 하고, 사용할 때는 Map을 사용하여 사용한 인터페이스를 선택하도록 구현한다.&lt;/p&gt;
&lt;pre id=&quot;code_1704542309294&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;enum DamageType {
    hitPoint,
    magicPoint
}

private fianl Map&amp;lt;DamageType, Damage&amp;gt; damages;

void applyDamage(final DamageType damageType, final int damageAmount) {
    final Damage damage = damages.get(damageType);
    damage.execute(damageAmount);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;다중 중첩 -&amp;gt; 인터페이스 (정책 패턴)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터페이스는 switch 조건문 뿐 아니라, 다중 중첩된 복잡한 분기를 제거하는 데도 활용할 수 있다. 다음 코드는 온라인 쇼핑몰에서 우수 고객인지 판정하는 다중 중첩이 사용된 로직이다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1704539727936&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 골드 회원 판정
boolean isGoldCustomer(PurchaseHistory history) {
    // 구매 금액이 100만원 이상 
    if (1000000 &amp;lt;= history.totalAmount) {
        // 한 달 구매 횟수 10회 이상
        if (10 &amp;lt;= history.purchaseFrequencyPerMonth) {
            // 반품률이 0.1% 이하
            if (history.returnRate &amp;lt;= 0.001) {
                return true;
            }
        }
    }
    return false;
}

// 실버 회원 판정
boolean isSilverCustomer(PurchaseHistory history) {
    // 구매 금액이 50만원 이상 
    if (500000 &amp;lt;= history.totalAmount) {
        // 한 달 구매 횟수 5회 이상
        if (5 &amp;lt;= history.purchaseFrequencyPerMonth) {
            // 반품률이 0.1% 이하
            if (history.returnRate &amp;lt;= 0.001) {
                return true;
            }
        }
    }
    return false;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;골드 회원과 실버 회원은 판정 조건이 거의 같지만, 같은 로직이 계속 작성되고 있다. 이러한 상황에서 유용하게 활용할 수 있는 패턴으로&amp;nbsp;&lt;b&gt;정책 패턴(policy pattern)&lt;/b&gt;이 있다. 조건을 부품처럼 만들고, 부품으로 만든 조건을 조합해서 사용하는 패턴이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 하나하나의 규칙(판정 조건 : 골드 회원 조건, 실버 회원 조건, 추후 추가될 수 있는 회원 등급 조건 등)을 나타내는 인터페이스를 만든다.&lt;/p&gt;
&lt;pre id=&quot;code_1704540100727&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface ExcellentCustomerRule {
    boolean ok(final PurchaseHistory history);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 각 회원 등급을 달성하기 위해 만족해야 각각의 조건을 ExcellentCustomerRule을 구현해서 만든다.&lt;/p&gt;
&lt;pre id=&quot;code_1704540329806&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 골드 회원 구매 금액 조건
class GoldCustomerPurchaseAmountRule implements ExcellentCustomerRule {
    public boolean ok(final PurchaseHistory history) {
        return 1000000 &amp;lt;= history.totalAmount;
    }
}

// 골드 회원 구매 빈도 조건
class GoldCustomerPurchaseFrequencyRule implements ExcellentCustomerRule {
    public boolean ok(final PurchaseHistory history) {
        return 10 &amp;lt;= history.purchaseFrequencyPerMonty;
    }
}

// 실버 회원 구매 금액 조건
class SilverCustomerPurchaseAmountRule implements ExcellentCustomerRule {
    public boolean ok(final PurchaseHistory history) {
        return 500000 &amp;lt;= history.totalAmount;
    }
}

// 실버 회원 구매 빈도 조건
class SilverCustomerPurchaseFrequencyRule implements ExcellentCustomerRule {
    public boolean ok(final PurchaseHistory history) {
        return 5 &amp;lt;= history.purchaseFrequencyPerMonty;
    }
}

// 공통 반품률 조건
class ReturnRateRule implements ExcellentCustomerRule {
    public boolean ok(final PurchaseHistory history) {
        return history.returnRate &amp;lt;= 0.001;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 각 조건들을 하나의 부품처럼 만들고, 필요한 조건들을 조합하여 각 등급을 판정하면 된다. 규칙을 집약하고, 판정하는 정책 클래스를 만들어보자.&lt;/p&gt;
&lt;pre id=&quot;code_1704540595698&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class ExcellentCustomerPolicy() {} {
    private final Set&amp;lt;ExcellentCustomerRule&amp;gt; rules;
    
    ExcellentCustomerPolicy() {
        rules = new HashSet();
    }
    
    /**
    * 정책에 사용할 규칙 추가
    *
    * @param rule 규칙
    */
    void add(final ExcellentCustomerRule rule) {
        rules.add(rule);
    }
    
    /**
    * @param history 구매 이력
    * @return 규칙을 모두 만족하는 경우 true
    */
    boolean complyWithAll(final PurchaseHistory history) {
        for (ExcellentCustomerRule each : rules) {
            if (!each.ok(history)) return false;
        }
        return true;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 각 회원 등급의 정책을 만들 때, 원하는 규칙을 추가하여 자유자재로 만들고 간편하게 수정할 수 있다. 이를 사용하여 골드 회원 정책을 만들어보자.&lt;/p&gt;
&lt;pre id=&quot;code_1704541048556&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class GoldCustomerPolicy {
    private final ExcellentCustomerPolicy policy;
    
    GoldCustomerPolicy() {
        policy = new ExcellentCustomerPolicy();
        policy.add(new GoldCustomerPurchaseAmountRule());
        policy.add(new GoldPurchaseFrequencyRule());
        policy.add(new ReturnRateRule());
        // 원하는 조건 자유롭게 추가
    }
    
    boolean complyWithAll(final PurchaseHistory history) {
        return policy.complyWithAll(history);
    }
}&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;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;조건 분기를 써야 하는 상황에는 일단 인터페이스 설계를 떠올리자&lt;/h4&gt;</description>
      <category>Study/내 코드가 그렇게 이상한가요?</category>
      <category>CleanCode</category>
      <category>java</category>
      <category>Refactoring</category>
      <category>내 코드가 그렇게 이상한가요?</category>
      <category>설계</category>
      <category>코딩</category>
      <category>클린코드</category>
      <author>aa_rong_blog</author>
      <guid isPermaLink="true">https://poalim.tistory.com/51</guid>
      <comments>https://poalim.tistory.com/51#entry51comment</comments>
      <pubDate>Sat, 6 Jan 2024 20:59:53 +0900</pubDate>
    </item>
    <item>
      <title>5. 응집도 : 흩어져 잇는 것들</title>
      <link>https://poalim.tistory.com/50</link>
      <description>&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;응집도(cohension)&lt;/b&gt;란 '모듈 내부에 있는 데이터와 로직 사이의 관계가 얼마나 강한지 나타내는 지표'이다. 여기서 모듈은 클래스, 패키지, 레이어 등을 모두 포함할 수 있다. &lt;b&gt;응집도가 높은 구조는 변경하기 쉬우며 바람직한 구조인 반면, 응집도가 낮은 구조는 변경 시 문제가 발생하기 쉽다. &lt;/b&gt;이러한 응집도를 낮추는 사례들을 알아보자.&lt;/p&gt;
&lt;h2 style=&quot;text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;5.1 static 메서드 오용&lt;/h2&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;static 메서드를 오용하면 응집도가 낮아질 수 있다.&lt;/b&gt; 아래 예시를 살펴보자.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;// 주문을 관리하는 클래스
class OrderManager {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;static int add(int moneyAmount1, int moneyAmount2) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;	return moneyAmount1 + moneyAmount2;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}

// static메서드를 사용하는 상황
moneyData1.amount = OrderManager.add(moneyData1.amount, moneyData2.amount);&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;이러한 경우 데이터는 MoneyData 클래스에 있고, 데이터를 조작하는 로직은 OrderManager에 있게 된다. 즉 &lt;b&gt;데이터와 로직이 서로 다른 클래스에 작성되게 되어 응집도가 낮은 구조&lt;/b&gt;가 된다.&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;5.1.1 static 메서드는 인스턴스 변수를 사용할 수 없음&lt;/h3&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;static 메서드는 인스턴스 변수를 사용할 수 없다.&lt;/b&gt; 따라서 static 메서드로 만든 시점에 이미 데이터와 데이터를 조작하는 로직 사이에 괴리가 생겨 응집도가 낮아진다.&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;5.1.2 인스턴스 변수를 사용하는 구조로 변경하기&lt;/h3&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;응집도란 '클래스 내부에서 데이터와 로직의 관계가 얼마나 강한지 나태내는 지표'&lt;/b&gt;라고 했다. 따라서 '인스턴스 변수'와 '인스턴스 변수를 사용하는 로직을 같은 클래스에 만드는 것이 응집도를 높이는 방법이다. OrderManager 클래스도 응집도가 높은 구조가 될 수 있도록 인스턴스 변수를 사용해서 계산하는 구조로 설계를 변경하는 것이 좋다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;// 주문을 관리하는 클래스 V2
class OrderManager {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;final int amount;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;int add(OrderManager increment) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;	return amount + increment.amount;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;5.1.3 인스턴스 메서드인 척하는 static 메서드 주의하기&lt;/h3&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;static 키워드가 붙어 있지 않을 뿐, static 메서드와 같은 문제를 갖고 있는 인스턴스 메서드도 주의해야 한다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;class PaymentManager {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private int discountRate;	// 할인율
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;int add(int moneyAmount1, int moneyAmount2) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;	return moneyAmount1 + moneyAmount2;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;위 코드에서 PaymentManager.add 메서드는 인스턴스 메서드이지만, 인스턴스 변수 discountRate를 전혀 사용하지 않는다. 이와 같은 메서드는 앞에 static 키워드를 붙여도 아무 문제 없이 동작한다.&lt;br /&gt;어떤 것이 인스턴스 메서드인 척하는 static 메서드인지 구분하는 쉬운 방법은, &lt;b&gt;메서드 앞에 static 키워드를 붙여보는 것&lt;/b&gt;이다. 메서드에서 인스턴스 변수가 사용되고 있다면 컴파일 오류가 발생할 것이다.&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;5.1.4 왜 static 메서드를 사용할까?&lt;/h3&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;static 메서드를 사용하는 이유는 데이터와 로직이 따로 존재하도록 설계하는 절차 지향적인 접근 방법에서 기인한다. 우리가 사용하는 &lt;b&gt;객체 지향 언어에서는 데이터와 로직을 하나의 클래스에 존재하도록 설계하여 응집도가 높은 코드를 설계해야 한다.&lt;/b&gt;&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;5.1.5 어떠한 상황에서 static 메서드를 사용해야 좋을까?&lt;/h3&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;static 메서드를 사용하면 좋은 상황도 물론 있다. 아래의 경우와 같은 응집도의 영향을 받지 않는 경우에는 static 메서드를 사용해도 좋다.&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;팩토리 메서드(5.2.1)&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;5.2 초기화 로직 분산&lt;/h2&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;클래스를 잘 설계해도, 초기화 로직이 분산되면 응집도가 낮은 구조가 될 수 있다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;class GiftPoint {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private static final int MIN_POINT = 0;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;final int value;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;GiftPoint(final int point) {	// Public 생성자
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;	if (point &amp;lt; MIN_POINT) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;throw new IllegalArgumentException(&quot;포인트를 0 이상 입력해야 합니다.&quot;);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;value = point;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;회원가입 시 지급하는 무료 포인트를 나타내는 위 클래스는 아래와 같이 사용될 수 있다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;// 표준 회원 가입
GiftPoint standardMembershipPoint = new GiftPoint(3000);

// 프리미엄 회원 가입
GiftPoint standardMembershipPoint = new GiftPoint(10000);&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;이 경우 회원 가입 포인트를 변경하고 싶을 때, 전체 소스를 확인해야 하는 응집도가 낮은 코드이다. 여러 곳에서 중복하여 인스턴스를 생성하여 사용하는 문제도 발생할 수 있다.&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;5.2.1 private 생성자 + 팩토리 메서드를 사용해 목적에 따라 초기화하기&lt;/h3&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;생성자를 private으로 만들고, 대신 목적에 따라 팩토리 메서드를 만들어보자.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;class GiftPoint {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private static final int MIN_POINT = 0;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private static final int STANDARD_MEMBERSHIP_POINT = 3000;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private static final int PREMIUM_MEMBERSHIP_POINT = 10000;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private GiftPoint(final int point) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;	if (point &amp;lt; MIN_POINT) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;throw new IllegalArgumentException(&quot;포인트는 0 이상이어야 합니다.&quot;);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;value = point;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;/**
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;* @return 표준 가입 기프트 포인트
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;*/
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;static GiftPoint forStandardMembership() {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;	return new GiftPoint(STANDARD_MEMBERSHIP_POINT);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;/**
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;* @return 프리미엄 가입 기프트 포인트
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;*/
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;static GiftPoint forPremiumMembership() {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;	return new GiftPoint(PREMIUM_MEMBERSHIP_POINT);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;생성자를 private으로 만들면, 클래스 내부에서만 인스턴스를 생성할 수 있어서 응집도를 높일 수 있다.&lt;/b&gt; 인스턴스를 생성하는 기능은 static 팩토리 메서드에서 생성자를 호출하도록 추가한다. GiftPoint 클래스를 사용하는 회원 가입 로직은 아래와 같이 팩토리 메서드를 사용하는 형태로 바뀌게 된다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;GiftPoint standardMembershipPoint = GiftPoint.forStandardMembership();
GiftPoint premiumMembershipPoint = GiftPoint.forPrimiumMembership();&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;b&gt;상황에 따라 생성 로직이 너무 많아진다면, 생성 전용 팩토리 클래스를 분리하는 방법을 고려하는 것이 좋다.&lt;/b&gt;&lt;/p&gt;
&lt;h2 style=&quot;text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;5.3 범용 처리 클래스 (Common/Util)&lt;/h2&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;Common, Util이라는 이름이 붙는 범용 처리 클래스는 static 메서드를 가장 빈번하게 볼 수 있는 클래스이다. 이러한 범용 처리 클래스도 여러 문제를 만들 수 있다.&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;5.3.1 너무 많은 로직이 한 클래스에 모이는 문제&lt;/h3&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;Common과 Util이라는 이름 자체가 '범용'이라는 뜻이기 때문에 이 코드를 읽는 사람은 '범용적으로 사용하고 싶은 로직은 Common 클래스에 모아 두면 되겠구나'라고 생각하게 만든다.&lt;br /&gt;근본적인 원인은 범용의 의미와 재사용성을 잘못 이해하고 있기 때문이다. 설계의 응집도를 높이면, 특정 데이터의 조작이 해당 클래스에서만 이루어지므로 재사용성이 저절로 높아지고, 범용적으로 사용될 일이 없어진다.&lt;br /&gt;&lt;b&gt;꼭 필요한 경우가 아니라면, 범용 처리 클래스를 만들지 않는 것이 좋다.&lt;/b&gt;&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;5.3.3 횡단 관심사&lt;/h3&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;로그 출력과 오류 확인 등 애플리케이션의 모든 동작에 필요한 기능들은 범용 코드로 만들어도 괜찮다. 다양한 상황에서 넓게 활용되는 기능을&amp;nbsp;&lt;b&gt;횡단 관심사(cross-cutting concern)&lt;/b&gt;라고 부르며, 아래와 같은 것들이 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;로그 출력&lt;/li&gt;
&lt;li&gt;오류 확인&lt;/li&gt;
&lt;li&gt;디버깅&lt;/li&gt;
&lt;li&gt;예외 처리&lt;/li&gt;
&lt;li&gt;캐시&lt;/li&gt;
&lt;li&gt;동기화&lt;/li&gt;
&lt;li&gt;분산 처리&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;5.4 결과를 리턴하는 데 매개변수 사용하지 않기&lt;/h2&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;출력으로 사용되는 매개변수를 출력 매개변수라고 부른다. &lt;b&gt;출력 매개변수는 응집도가 낮아지는 문제를 발생시킨다.&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;class ActorManager {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// 게임 캐릭터 위치를 이동
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;void shift(Location location, int shiftX, int shiftY) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;	location.x += shiftX;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;location.y += shifty;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;ActorManager.shift 메서드는 이동 대상 인스턴스를 location으로 전달받고, 이를 변경한다. 즉, 조작되는 데이터의 대상은 Location 클래스에 있고, 조작 로직은 ActorManager 클래스에 있는 응집도가 낮은 구조이다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;b&gt;매개변수는 입력으로 전달하는 것이 일반적이다.&amp;nbsp;&lt;/b&gt;매개변수를 출력으로 사용해 버리면, 매개변수가 입력인지 출력인지 매번 메서드 내부 로직을 확인해야 하여 가독성이 좋지 않다. &lt;b&gt;'데이터'와 '데이터를 조작하는 로직'은 하나의 클래스에 배치하여 응집도를 높인다.&lt;/b&gt;&lt;/p&gt;
&lt;h2 style=&quot;text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;5.5 매개변수가 너무 많은 경우&lt;/h2&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;매개변수가 너무 많은 메서드는 응집도가 낮아지기 쉽다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;/*
* 매직포인트 회복하기
* currentMaxMagicPoint 현재 매직포인트 잔량
* originalMaxMagicPoint 원래 매직포인트 최댓값
* maxMagicPointIncrements 장비로 증가하는 매직포인트 최댓값 증가량
* recoveryAmount 회복량
* @return 회복 후의 매직포인트 잔량
*/

int recoverMagicPoint(int currentMaxMagicPoint, int originalMaxMagicPoint, List&amp;lt;Integer&amp;gt; maxMagicPointIncrements, int recoveryAmount) {
	int currentMaxMagicPoint = originalMaxMagicPoint;
	for (int each : maxMagicPointIncrements) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;	currentMaxMagicPoint += each;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
	return Math.min(currentMagicPoint + recoveryAmount, currentMagicPoint);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;위와 같이 너무 많은 매개변수를 받는 메서드는 실수로 잘못된 값을 대입할 가능성이 높아진다.&amp;nbsp; 메서드에 매개변수를 전달한다는 것은 해당 매개변수를 사용해서 어떤 기능을 수행하고 싶다는 의미이다. 즉, &lt;b&gt;매개변수가 많다는 것은 많은 기능을 처리하고 싶다는 의미&lt;/b&gt;가 된다. 하지만, &lt;b&gt;처리할 게 많아지면 로직이 복잡해지거나, 중복 코드가 생길 가능성이 높아진다.&lt;/b&gt;&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;5.5.1 기본 자료형에 대한 집착&lt;/h3&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;recoverMagicPoint 메서드는 매개변수와 리턴 값에 모두 기본 자료형만 쓰고 있다. 이와 같이 기본 자료형을 남용하는 현상을 &lt;b&gt;기본 자료형 집착(primitive obession)&lt;/b&gt;이라고 한다.&lt;br /&gt;기본 자료형만으로도 '동작하는 코드'를 작성할 수 있지만, 관련 있는 데이터와 로직을 집약하기 힘들고 중복 코드가 많이 생긴다. 이는 버그가 생기기 쉽고, 가독성이 떨어지는 코드로 이어진다.&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;5.5.2 의미 있는 단위는 모두 클래스로 만들기&lt;/h3&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;매개변수가 너무 많아지는 문제를 피하려면, 기본 자료형 집착에서 벗어나서 개념적으로 의미 있는 클래스를 만들면 된다.&amp;nbsp; recoverMagicPoint를 개선하기 위해 매직포인트를 나타내는 클래스 MagicPoint를 만들어보자.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;class MagicPoint {
	private int currentAmount;
	private int originalMaxAmount;
	private final List&amp;lt;Integer&amp;gt; maxIncrements;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// 현재 매직포인트 잔량
	int current() {
		return currentAmount;
	}
	
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// 매직포인트 최댓값
	int max() {
		int amount = originalMaxAmount;
		for (int each : maxIncrements) {
			amount += each;
		}
		return amount;
	}
	
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// 매직포인트 회복하기
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;void recover(final int recoveryAmount) { ... }
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// 매직포인트 소비하기
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;void consume(final int consumeAmount) { ... }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;매직포인트와 관련된 인스턴스 변수들을 하나로 모으고, 관련된 로직들도 메서드로 만들어 한 클래스에 작성한다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;int recoverMagicPoint(MagicPoint magicPoint, int recoveryAmount) {
	// ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;이에 따라 recoveryMagicPoint는 위와 같이 변경된다.&lt;/p&gt;
&lt;h2 style=&quot;text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;5.6 메서드 체인&lt;/h2&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;아래와 같이 .(점)으로 여러 메서드를 연결해서 리턴 값의 요소에 차례차례 접근하는 방법을 &lt;b&gt;메서드 체인&lt;/b&gt;이라고 한다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;/** 갑옷 입기
* @param memberId 장비를 변경하고 싶은 멤버의 ID
* @param newArmor 입을 갑옷
*/
void equipArmor(int memberId, Armor newArmor) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if(party.members[memberId].equipments.canChange) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;	party.members[memberId].equipments.armor = newArmor;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이 방법도 응집도를 낮출 수 있는 좋지 않은 작정 방법이다.&lt;/b&gt; armor, member, equipments에 할당하는 코드는 어디에서나 작성될 수 있고, 이는 코드의 중복을 유발한다.&lt;/p&gt;
&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;데메테르의 법칙&lt;/h4&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;데메테르의 법칙은 사용하는 객체 내부를 알아서는 안 된다는 법칙&lt;/b&gt;이다. (모르는 사람에게 말을 걸지 않기). 메서드 체인으로 내부 구조를 돌아다닐 수 있는 설계는 항상 객체의 내부 구조를 알아야 하므로 데메테르의 법칙을 위반한다.&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;5.6.1 묻지 말고 명령하기&lt;/h3&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;소프트웨어 설계에는 &lt;b&gt;'묻지 말고, 명령하기(Tell, Don't Ask, TDA)'&lt;/b&gt;라는 유명한 격언(법칙?)이 있다. 이는 다른 객체 내부 상태(변수)를 기반으로 판단하거나 제어하려고 하지 말고, 메서드로 명령해서 객체가 알아서 판단하고 제어하도록 설계하라는 의미이다. (데메테르의 법칙과 일통하는 부분이 있다.).&lt;br /&gt;이를 적용한 좋은 설계는 인스턴스 변수를 private으로 변경해서 외부에서 접근할 수 없게 하고, 인스턴스 변수에 대한 제어는 외부에서 메서드로 명령하는 형태로 객체를 설계하여 상세한 판단과 제어는 명령을 받는 쪽에서 담당하게 하면 된다. 이에 따라 장비하고 있는 방어구 목록을 나타내는 Equipments 클래스를 정의해보자.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;class Equipments {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private boolean canChange;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private Equipment head;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private Equipment armor;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private Equipment arm;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;/**
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;* 갑옷 장비하기
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;* @param newArmor 장비할 갑옷
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;*/
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;void equipArmor(final Equipment newArmor) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;	if (canChange) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;armor = newArmor;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 하면 방어구의 탈착과 관련된 로직이 Equipments 클래스에 응집된다.&amp;nbsp;&lt;/p&gt;</description>
      <category>Study/내 코드가 그렇게 이상한가요?</category>
      <category>CleanCode</category>
      <category>java</category>
      <category>Refactoring</category>
      <category>내 코드가 그렇게 이상한가요?</category>
      <category>설계</category>
      <category>코딩</category>
      <category>클린코드</category>
      <author>aa_rong_blog</author>
      <guid isPermaLink="true">https://poalim.tistory.com/50</guid>
      <comments>https://poalim.tistory.com/50#entry50comment</comments>
      <pubDate>Wed, 20 Dec 2023 21:48:48 +0900</pubDate>
    </item>
    <item>
      <title>4. 불변 활용하기 (안정적으로 동작하게 만들기)</title>
      <link>https://poalim.tistory.com/49</link>
      <description>&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;가변과 불변을 적절하게 설계하지 못하면 동작을 예측하기 어렵고 혼란스러워진다. 이러한 상황을 피하기 위해 가능한 한 상태가 변경되지 않도록 불변을 활용하여 설계하는 방법을 알아본다.&lt;/p&gt;&lt;h2 style=&quot;text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;4.1 재할당&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&lt;b&gt;재할당(파괴적 할당)&lt;/b&gt; : 변수에 값을 다시 할당하는 것&lt;br&gt;재할당은 변수의 의미를 바꿔 추측하기 어렵게 만들고, 언제 어떻게 변경되었는지 추적하기도 힘들게 만든다.&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;int damage() {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// 기본 공격력
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;int tmp = member.power() + member.weaponAttack();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// 속도로 공격력 보정
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;tmp = (int)(tmp&amp;nbsp;&amp;nbsp;(1f + member.speed() / 100f));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// 적 방어력 적용
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;tmp = tmp - (int)(enemy.defence / 2);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// 음수 조정
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;tmp = Math.max(0, tmp);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return tmp;
}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;위 코드는 변수 tmp 값에 기본 공격력, 보정 값, 데미지 등이 계속 재할당되면서 값의 의미가 바뀐다. 이는 읽는 사람이 헷갈릴 수 밖에 없게 만들고, 버그의 원인이 되므로 이러한 &lt;b&gt;재할당은 피하는 것이 좋다.&lt;/b&gt; 좋은 대안은 의미를 갖는 새로운 값이 있을 때마다 새로운 변수를 만들어 사용하는 것이다.&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;4.1.1 불변 변수로 만들어서 재할당 막기&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;의도치 않게 사용하는 &lt;b&gt;재할당을 기계적으로 막는 방법은 변수를 불변으로 만드는 것&lt;/b&gt;이다. java 언어 기준으로 변수에 final 수식자를 붙이면 된다. 불변으로 만든 변수는 재할당 시 컴파일 오류가 발생한다.&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;int damage() {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;final int basicAttackPower = member.power() + member.weaponAttck();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;final int finalAttackPower = (int)(basicAttackPower * (1f + member.speed() / 100f));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;final int reduction = (int)(enemy.defence / 2);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;final int damage = Math.max(0, finalAttackPower - reduction);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return damage;
}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;tmp 변수를 의미를 가진 각각의 변수로 나누고 불변으로 만들어서 재할당을 제거하였다.&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;4.1.2 매개변수도 불변으로 만들기&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;매개변수도 마찬가지로 그 값이 변경되면 읽는 사람을 헷갈리게 만들고, 버그의 원인이 될 수 있다. 이러한 &lt;b&gt;재할당을 막기 위해서 매개변수도 역시 불변으로 만들면 된다.&lt;/b&gt; 만약 매개변수에 어떠한 연산을 적용하고 싶다면, 불변 지역 변수를 만들고 이를 활용해서 연산을 한다.&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;void addPrice(final int productPrice) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;final int increasedTotalPrice = totalPrice + productPrice;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (MAX_TOTAL_PRICE &amp;lt; increasedTotalPrice) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;	throw new IllegalArgumentException(&quot;구매 상한 금액을 넘었습니다.&quot;);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/code&gt;&lt;/pre&gt;&lt;h2 style=&quot;text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;4.2 가변으로 인해 발생하는 의도하지 않은 영향&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;그렇다면 가변일 경우 의도치 않은 영향을 끼치는 경우를 살펴보자.&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;4.2.1 사례 1 : 가변 인스턴스 재사용하기&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;게임을 예로 들어서 설명한다. 만약 아래와 같이 공격력을 나타내는 AttackPower 클래스가 있다고 하자.&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;class AttackPower {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;static final int MIN = 0;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;int value;	// 가변 변수
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;AttackPower(int value) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;	if (value &amp;lt; MIN) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;throw new IllegalArgumentException();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.value = value;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;해당 클래스를 사용하여 A, B 두 개의 무기에 같은 공격력(20)을 할당했다고 하자.&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;AttackPower attackPower = new AttackPower(20);

Weapon weaponA = new Weapon(attackPower);
Weapon weaponB = new Weapon(attackPower);&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;이 경우 &lt;b&gt;A 무기의 공격력을 강화하면 B 무기의 공격력도 같이 강화되는&lt;/b&gt; 치명적인 버그가 발생한다. 이러한 코드를 짜는 것 자체가 말이 안 된다고 생각할 수 있지만, 만약 첫 기획이 모든 무기는 같은 공격력을 가진다라는 가정으로 시작했다면 충분히 있을 법 하다. (서비스의 기능이 수시로 변하고 업데이트 되는 것은 흔한 일이다.)&lt;br&gt;이러한 상황을 예방하려면, 인스턴스를 재사용하지 못하게 만들면 된다. 각 무기의 AttackPower 인스턴스를 개별적으로 생성하고, 재사용하지 않는 로직으로 변경해보자.&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;AttackPower attackPowerA = new AttackPower(20);
AttackPower attackPowerB= new AttackPower(20);

Weapon weaponA = new Weapon(attackPowerA);
Weapon weaponB = new Weapon(attackPowerB);&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;다만 위 코드도 AttackPower 클래스의 value 값 자체가 아직 가변이기 때문에 위와 같은 상황을 인지하고 설계하지 않는다면 언제든 버그가 재발할 수 있다.&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;4.2.2 사례2 : 함수로 가변 인스턴스 조작하기&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;예상하지 못한 동작은 함수(메서드) 때문에 발생하기도 한다. AttackPower 클래스에 공격력을 변화시키는 reinforce(강화), disable(무력화) 메서드를 추가해보자.&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;class AttackPower {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;static final int MIN = 0;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;int value;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;AttackPower(int value) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;	if (value &amp;lt; MIN) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;throw new IllegalArgumentException();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.value = value;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;/**
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;* 공격력 강화하기
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;* @param increment 공격력 증가량
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;*/
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;void reinforce(int increment) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;	value += increment;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;/** 무력화하기 */
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;void disable() {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;	value = MIN;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;위의 코드에서 처음에는 정상적으로 동작했지만 &lt;b&gt;어느날 갑자기 공격력이 0이 되는 일이 종종 발생하는 버그&lt;/b&gt;가 생겼다. 원인을 조사해보니, AttackPower 인스턴스가 다른 스레드에서 사용된 것이 원인이었다. 다른 스레드에서 disable() 메서드의 실행이 어떤 이슈로 지연되었고, 실행되지 않아야 할 순서에 실행된 것이다.&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;AttackPower attackPower = new AttackPower(20);
// ...
attackPower.reinforce(15);

// 다른 스레드의 처리
attackPower.disable();&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;AttackPower의 disable 메서드와 reinforce 메서드는 구조적인 문제를 갖고 있다. 바로 부수효과 이다.&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;4.2.3 부수 효과의 단점&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&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;주요 작용 : 함수(메서드)가 메개 변수를 전달 받고, 값을 리턴하는 것&lt;/b&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;부수 효과 : 주요 작용 이외의 상태 변경을 일으키는 것&lt;/b&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;여기서 상태 변경이란 아래와 같은 함수 밖에 있는 상태를 변경하는 것을 의미한다.&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;인스턴스 변수 변경&lt;/li&gt;&lt;li&gt;전역 변수 변경&lt;/li&gt;&lt;li&gt;매개변수 변경&lt;/li&gt;&lt;li&gt;I/O 조작(파일 읽고 쓰기 등)&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;4.2.2 코드의 reinforce와 disable에서 value라는 &lt;b&gt;상태 변경이 포함된 코드는, 버그가 없는 동일한 결과를 얻기 위해서 동일한 순서로 실행이 되어야 한다.&lt;/b&gt; 이는 결과가 작업 실행 순서에 의존하게 되며, 예측할 수 없는 결과를 만들어낸다. 인스턴스 변수 뿐아니라 위에 나열한 전역 변수, 매개변수, 파일 I/O 조작도 모두 마찬가지이다. 다만, 함수 내부에 선언한 지역 변수는 함수 외부에 영향을 주지 않기 때문에 부수 효과라고 할 수 없다.&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;4.2.4 함수의 영향 범위 한정하기&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;함수의 예상치 못한 동작을 막기 위해서는&amp;nbsp;&lt;b&gt;함수가 영향을 주거나 받을 수 있는 범위를 한정하는 것&lt;/b&gt;이 좋다. 다음과 같은 항목을 만족하도록 함수를 설계하자.&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;데이터(상태)는 매개 변수로 받는다.&lt;/li&gt;&lt;li&gt;상태를 변경하지 않는다.&lt;/li&gt;&lt;li&gt;값은 함수의 리턴 값으로 돌려준다.&lt;/li&gt;&lt;/ul&gt;&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;4.2.5 불변으로 만들어서 예기치 못한 동작 막기&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;그럼 모든 문제의 근원이었던 가변 인스턴스 변수 value를 불변으로 변경해보자.&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;class attackPower {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;static final int MIN = 0;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;final int value;	// 불변
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;AttackPower(final int value) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;	if(value &amp;lt; MIN) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;throw new IllegalArgumentException();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.value = value;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;/** 공격력 강화하기
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;* @param increment 공격력 증가량
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;* @return 증가된 공격력
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;*/
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;AttackPower reinforce(final AttackPower increment) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;	return new AttackPower(this.value + increment.value);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;/**
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;* 무력화하기
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;* @return 무력화한 공격력
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;*/
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;AttackPower disable() {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;	return new AttackPower(MIN);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;value를 불변으로 만들고 reinforce와 disable 메서드에서 새로운 인스턴스를 만들어 반환하도록 변경하였다. 위 코드를 사용하여 reinforce, disable을 호출하는 코드도 변경해보자.&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;final AttackPower attackPower = new AttackPoweR(20);
// ...
final AttackPower reinforced = attackPower.reinforce(new AttackPower(15));

// 다른 스레드에서 처리
final AttackPower disabled = attackPower.disable();&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;불변으로 만들어 재할당을 막음으로서, reinforce, disable을 통해 변경된 값은 새로운 인스턴스에 저장되어야 한다. 이는 &lt;b&gt;변경 전과 변경 후의 공격력이 서로 영향을 주지 않게 한다.&lt;/b&gt;&lt;/p&gt;&lt;h2 style=&quot;text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;4.3 불변과 가변은 어떻게 다루어야 할까&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;실제로 개발을 할 때, 불변과 가변을 어떻게 다루어야 할 지 알아본다.&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;4.3.1 기본적으로 불변으로&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;변수를 불변으로 만들면 다음과 같은 장점이 있다.&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;변수의 의미가 변하지 않으므로, 혼란을 줄일 수 있다.&lt;/li&gt;&lt;li&gt;동작이 안정적이게 되므로, 결과를 예측하기 쉽다.&lt;/li&gt;&lt;li&gt;코드의 영향 범위가 한정적이므로, 유지 보수가 편리해진다.&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;따라서 기본적으로 불변으로 설계하는 것이 좋다. 비교적 최근에 등장한 러스트(Rust) 언어에서는 불변이 기본 값이고, 가변으로 만들려면, mut 키워드를 붙여야 한다. 이를 통해 불변이라는 성질을 중요하게 여기는 최근 프로그래밍 언어의 추세를 알 수 있다.&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;4.3.2 가변으로 설계해야 하는 경우&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;가변이 필요한 경우도 당연히 존재한다. 바로 &lt;b&gt;성능(performance)&lt;/b&gt;이 중요한 경우이다. 불변의 값을 변경할 때는 인스턴스를 매번 새로 생성해야 하므로, 크기가 큰 인스턴스를 매번 새로 생성하면서 성능에 문제가 생긴다면, 불변보다는 가변을 사용하는 것이 좋다. (ex. 대량의 데이터를 빠르게 처리해야 하는 경우, 이미지 처리, 리소스에 제약이 큰 임베디드 소프트웨어를 다루는 경우 등)&lt;br&gt;또한 &lt;b&gt;범위(scope)가 국소적인 경우&lt;/b&gt;에도 가변을 사용해도 좋다. 예를 들어 반복문 카운터 등 반복 처리 스코프에서만 사용되는 지역 변수는 가변이라도 괜찮다.&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;4.3.3 상태를 변경하는 메서드 설계하기&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;인스턴스 변수를 가변으로 만들었다면, 메서드를 만들 때 항상 주의해야 한다. 메서드에서 상태를 변경할 때 항상 조건에 맞는 올바른 상태가 되도록 변경하여야 한다.&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;/** 대미지 받는 처리
* @param damageAmount 대미지 크기
*/
void damage(final int damageAmount) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;final int nextAmount = amount - damageAmount;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;amount = Math.max(MIN, nextAMount);
}&lt;/code&gt;&lt;/pre&gt;&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;4.3.4 코드 외부와 데이터 교환은 국소화 하기&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;파일을 읽고 쓰는 I/O 조작이나 데이터베이스 접근 등은 코드를 아무리 주의 깊게 작성하여도 외부 상태이므로 제어할 수 없는 상황이 생긴다. (파일이나 데이터베이스가 다른 시스템에 의해 덮어 쓰이는 경우 등)&lt;br&gt;이러한 영향을 그나마 줄일 수 있도록, 코드 외부와 데이터 교환을 국소화 하는 테크닉을 사용한다. 국소화하는 방법으로는 &lt;b&gt;리포지토리 패턴(repository pattern)&lt;/b&gt;이 있다. 리포지토리 패턴은 데이터베이스의 영속화(persistence)를 캡슐화하는 디자인 패턴이다.&amp;nbsp;&lt;/p&gt;</description>
      <category>Study/내 코드가 그렇게 이상한가요?</category>
      <category>clean code</category>
      <category>java</category>
      <category>내 코드가 그렇게 이상한가요?</category>
      <category>설계</category>
      <category>코딩</category>
      <category>클린코드</category>
      <author>aa_rong_blog</author>
      <guid isPermaLink="true">https://poalim.tistory.com/49</guid>
      <comments>https://poalim.tistory.com/49#entry49comment</comments>
      <pubDate>Wed, 20 Dec 2023 11:07:26 +0900</pubDate>
    </item>
    <item>
      <title>3. 클래스 설계 (객체 지향 설계의 기본)</title>
      <link>https://poalim.tistory.com/48</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;객체 지향 설계의 기본이라고 할 수 있는 클래스 설계의 기본을 알아본다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3.1 클래스 단위로 잘 동작하도록 설계하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 중요한 것은 &lt;b&gt;'클래스 단위로도 잘 동작하게 설계해야 한다'&lt;/b&gt;는 것이다. 클래스는 클래스 하나로도 잘 동작할 수 있어야 하며, 이는 복잡한 초기 설정 없이 바로 사용할 수 있으며, 클래스를 마음대로 조작해서 클래스 전체가 고장나는 일이 없도록 최소한의 조작 방법(메서드)만 외부에 제공해야 한다는 의미이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스는 기본적으로 &lt;b&gt;'인스턴스 변수', '메서드'&lt;/b&gt; 두 가지로 구성된다. 여기서 잘 만들어진 클래스는 이를 다음과 같이 쓸 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인스턴스 변수&lt;/li&gt;
&lt;li&gt;인스턴스 변수에 &lt;b&gt;잘못된 값이 할당되지 않게 막고, 정상적으로 조작하는 메서드&lt;/b&gt;&lt;/li&gt;
&lt;/ul&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;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 구성은 클래스가 자기 자신을 보호할 수 있는 로직이 없으므로, 다른 클래스가 여러 가지 준비를 해줘야만 잘 작동하고, 혼자서는 아무것도 할 수 없는 미성숙한 클래스를 유발한다. 이는 처음에 말했던, '클래스 단위로도 잘 동작하게 설계해야 한다.'는 원칙을 위배한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면, 어떻게 하면 좋은 클래스를 설계할 수 있는 것일까? 굉장히 간단하다. &lt;b&gt;클래스에 자기 방어 임무를 부여해서, 다른 클래스에 맡기던 일을 스스로 할 수 있게 설계하면 된다.&lt;/b&gt; 이에 대해 자세한 내용을 이어서 알아보자.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3.2 성숙한 클래스로 성장시키는 설계 기법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;성숙한 클래스의 설계 기법을 설명하기 위해 금액을 나타내는 Money라는 클래스를 예로 들어서, 이 클래스를 성숙한 클래스로 성장시키는 과정을 살펴보자. Money는 인스턴스 변수만 가지고 있는 전형적인 데이터 클래스이다.&lt;/p&gt;
&lt;pre id=&quot;code_1701535694647&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.util.Currency;

class Money {
    int amount;		// 금액
    Currency currency;	// 통화 단위
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3.2.1 생성자로 확실하게 정상적인 값 설정하기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 Money 클래스는 &lt;b&gt;'초기화되지 않은 상태'&lt;/b&gt;를 유발하는 클래스 구조이다. 이러한 상태를 방지하기 위해서, 클래스 인스턴스를 생성하는 시점에 확실하게 인스턴스 변수가 정상적인 값을 갖게 만들어야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1701721352459&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 생성자에서 초기화하기
class Money {
    int amount;
    Currency currency;
    
    Money(int amount, Currency currenc) {
        this.amount = amount;
        this.currency = currency;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 클래스를 생성할 때, 생성자에서 인스턴스 변수가 무조건 초기화 된다. 다만, 위 상태는 생성자의 매개변수로 잘못된 값(-100, null, ..)이 전달될 경우 인스턴스 변수가 잘못된 값으로 초기화 될 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1701721671034&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 생성자에서 유효성 검사하기
class Money {
    // 생략
    Money(int amount, Currency currency) {
        if (amount &amp;lt; 0) {
            throw new IllegalArgumentException(&quot;금액은 0 이상의 값을 지정해 주세요.&quot;);
        }
        if (currency == null) {
            throw new NullPointerException(&quot;통화 단위를 지정해 주세요.&quot;);
        }
        
        this.amount = amount;
        this.currency = currency;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 생성자에서 유효성 검사를 하면 올바른 값만 인스턴스 변수에 저장되도록 할 수 있다. 위의 생성자와 같이 처리 범위를 벗어나는 조건을 메서드 가장 앞부분에서 확인하는 코드를 &lt;b&gt;가드(guard)&lt;/b&gt;라고 부른다. &lt;b&gt;가드를 활용하면 잘못된 값을 가진 인스턴스가 존재할 수 없게 되어서&lt;/b&gt;, 항상 정상적인 인스턴스만 존재하게 된다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3.2.2 계산 로직도 데이터를 가진 쪽에 구현하기&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;b&gt;'응집도가 낮은 구조'&lt;/b&gt;라고 한다. 응집도가 낮은 구조에서는 여러 가지 문제가 발생할 수 있으므로, 다른 클래스에게 맡겼던 일을 스스로 할 수 있게 만들어서 클래스를 성숙하게 만들어야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1701721962864&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Money 클래스에 금액을 추가하는 메서드 만들기
class Money {
    // 생략
    void add(int other) {
        amount += other;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3.2.3 불변 변수로 만들어서 예상하지 못한 동작 막기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인스턴스 변수의 값이 계속해서 바뀌면, 값이 언제 변경되었는지, 지금 값은 무엇인지 계속 신경을 써야 한다. 또한, 비즈니스 요구 사항이 바뀌어서 코드를 수정하다가 의도하지 않은 값을 할당하는 '예상치 못한 부수 효과'가 쉽게 발생할 수 있다. 이를 방지하기 위해, 인스턴스 변수를 &lt;b&gt;불변(immutable)&lt;/b&gt;으로 만들어서 값을 한 번 할당하면 다시는 바꿀 수 없는 변수로 만든다.&lt;/p&gt;
&lt;pre id=&quot;code_1701722261006&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// final을 붙여 불변 변수로 만들기
class Money {
    final int amount;
    final Currency currency;
    
    Money(int amount, Currency currency) {
        // 생략
        this.amount = amount;
        this.currency = currency;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 Money 클래스는 생성자를 통해 생성된 이후에 인스턴스 변수에 잘못된 값을 직접 할당할 수 없다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3.2.4 변경하고 싶다면 새로운 인스턴스 만들기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인스턴스 변수의 변경이 필요할 때는, 직접 인스턴스 변수의 내용을 변경하는 것이 아니라, 변경된 값을 가진 새로운 인스턴스를 만들어서 사용한다.&lt;/p&gt;
&lt;pre id=&quot;code_1701722535497&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 변경된 값을 가진 인스턴스 생성하기
class Money {
    // 생략
    Money add(int other) {
        int added = amount + other;
        return new Money(added, currency);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3.2.5 메서드 매개변수와 지역 변수도 불변으로 만들기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메서드 매개변수와 지역 변수도 인스턴스 변수와 마찬가지로 값이 중간에 바뀌면 값의 변화를 추적하기 힘들기 때문에 버그를 발생시키기도 한다. 따라서, 메서드 내부에서 매개변수를 변경하지 못하도록 매개변수도 불변으로 지정한다.&lt;/p&gt;
&lt;pre id=&quot;code_1701722723553&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// add 메서드의 매개 변수도 final로 만들기
class Money {
    // ...
    Money add(final int other) {
        int added = amount + other;
        return new Money(added, currency);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지역 변수도 마찬가지로, 재할당으로 인하여 값의 의미가 중간에 변경되지 않도록 불변으로 만들어준다.&lt;/p&gt;
&lt;pre id=&quot;code_1701722828752&quot; class=&quot;java&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;// 지역 변수도 불변으로 만들기
class Money {
    // ...
    Money add(final int other) {
        final int added = amount + other;
        return new Money(added, currency);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3.2.6 엉뚱한 값을 전달하지 않도록 하기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 add 메서드는 매개 변수를 불변으로 지정했음에도 불구하고 매개 변수로 엉뚱한 값이 전달될 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1701723128718&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 금액을 의미하지 않는 값을 전달하는 경우
final int ticketCount = 3;
money.add(ticketCount);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 코드는 add 메서드를 이용하여 가격이 아니라 티켓의 수를 더해버렸다. 명백한 버그이지만, 둘 다 int 자료형이므로 코드는 문제없이 실행되고 버그를 찾기도 쉽지 않다. int와 String과 같은 &lt;b&gt;기본 자료형&lt;/b&gt;(primitive type)은 의미가 다른 값이 여러 개 있어도 모두 int, String으로 정의하기가 쉽다. 따라서, 실수로 의미가 다른 값을 전달하지 않기 위해 Money와 같은 독자적인 자료형을 사용하면, 의미가 다른 값을 전달할 경우 컴파일 오류가 발생하여 이를 방지해준다.&lt;/p&gt;
&lt;pre id=&quot;code_1701723480618&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Money 자료형만 받도록 메서드 수정하고 개선하기
class Money {
    // ...
    Money add(final Money other) {
        if (!currency.equals(other.currency)) {
            throw new IllegalArgumentException(&quot;통화 단위가 다릅니다.&quot;);
        }
        
        final int added = amount + other.amount;
        return new Money(added, currency);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3.2.7 의미 없는 메서드 추가하지 않기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;금액을 나타내는 Money 클래스에서 합계 금액을 구하려면 가산(덧셈), 할인을 하려면 감산(뺄셈), 비율을 구하려면 나눗셈을 사용할 수 있다. 하지만 금액을 곱하는 일은 일반적인 회계 서비스에서 있을 수 없다. 이러한 경우, &lt;b&gt;의미 없는 메서드(곱셈)을 추가하지 않도록 하자&lt;/b&gt;. '덧셈, 뺄셈, 나눗셈 만드는 김에 곱셈도 구현해 두자.' 라는 생각은, 이후에 누군가 이를 무심코 사용했을 때 버그가 될 수 있다. 시스템 사양에 필요한 메서드만 정의하자.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3.3 결과&lt;/h3&gt;
&lt;pre id=&quot;code_1701723903991&quot; class=&quot;java&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;import java.util.Currency;

class Money {
    final int amount;
    final Currency currency;

    Money(final int amount, final Currency currency) {
        if (amount &amp;lt; 0) {
            throw new IllegalArgumentException(&quot;금액은 0 이상의 값을 지정해 주세요.&quot;);
        }
        if (currency == null) {
            throw new NullPointerException(&quot;통화 단위를 지정해 주세요.&quot;);
        }
        
        this.amount = amount;
        this.currency = currency;
    }
    
    Money add(final Money other) {
        if (!currency.equals(other.currency) {
            throw new IllegalArgumentException(&quot;통화 단위가 다릅니다.&quot;);
        }
        
        final int added = amount + other.amount;
        return new Money(added, currency);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;클래스 설계란 인스턴스 변수가 잘못된 상태에 빠지지 않게 하기 위한 구조를 만드는 것&lt;/b&gt;이라고 해도 과언이 아니다. 위의 Money 클래스처럼 관련된 로직이 한 곳에 모여 있는 구조를&amp;nbsp;&lt;b&gt;응집도가 높은 구조&lt;/b&gt;라고 한다. 또한, '데이터'와 그 '데이터를 조작하는 로직'을 하나의 클래스로 묶고, 필요한 절차(즉 메서드)만 외부에 공개하는 것을&amp;nbsp;&lt;b&gt;캡슐화&lt;/b&gt;라고 한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3.4 프로그램 구조의 문제 해결에 도움을 주는 디자인 패턴&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;응집도가 높은 구조로 만들거나, 잘못된 상태로부터 프로그램을 방어하는 등 프로그램의 구졸르 개선하는 설계 방법을&amp;nbsp;&lt;b&gt;디자인 패턴&lt;/b&gt;(설계 패턴, design pattern)이라고 부른다. 이 장에서 사용된 Money 클래스는 완전 생성자와 값 객체라는 두 가지 디자인 패턴을 적용한 것이다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3.4.1 완전 생성자&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;완전 생성자&lt;/b&gt;(complete constructor)는 잘못된 상태로부터 클래스를 보호하기 위한 디자인 패턴이다. 인스턴스 변수를 모두 초기화해야만 객체를 생성할 수 있게 매개변수를 가진 생성자를 만들고, 생성자 내부에 가드를 사용해서 잘못된 값이 들어오지 못하게 만들면, 값이 모두 정상인 완벽한 객체만 만들어질 것이다. 위 Money 클래스의 생성자가 바로 완전 생성자 구조이다. 또한, 인스턴스 변수에 final 수식자를 붙여서 불변으로 만들면, 생성 후에도 잘못된 상태로부터 방어할 수 있다. &lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3.4.2 값 객체&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;값 객체&lt;/b&gt;(value object)란 값을 클래스(자료형)로 나타내는 디자인 패턴이다. 예를 들어, 금액을 단순한 int 자료형의 지역 변수 또는 매개 변수로 사용하면, 금액 계산 로직이 이곳저곳에 분산되어 응집도가 낮은 구조가 되며, 실수로 의미가 다른 값들이 섞일 수도 있게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;값 객체를 사용하면 이러한 상황을 막을 수 있다. Money 클래스는 생성자에서 금액에 제약 조건(0원 이상)을 걸고 있으며, 금액을 계산하는 로직도 Money.add 메서드로 갖고 있어서 응집도가 높은 구조라고 할 수 있다. 또한, Money.add 메서드는 매개 변수로 Money 자료형만 받을 수 있으므로 의도하지 않게 다른 값이 섞이는 상황을 원천적으로 차단할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;값 객체와 완전 생성자는 얻을 수 있는 효과가 거의 비슷하므로 일반적으로 함께 사용되며,&amp;nbsp;&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;참고 - '내 코드가 그렇게 이상한가요?' 3장 클래스 설계: 모든 것과 연결되는 설계 기반&lt;/p&gt;</description>
      <category>Study/내 코드가 그렇게 이상한가요?</category>
      <category>java</category>
      <category>Refactoring</category>
      <category>설계</category>
      <author>aa_rong_blog</author>
      <guid isPermaLink="true">https://poalim.tistory.com/48</guid>
      <comments>https://poalim.tistory.com/48#entry48comment</comments>
      <pubDate>Tue, 5 Dec 2023 06:41:31 +0900</pubDate>
    </item>
    <item>
      <title>[Linux] telnet 비활성화 시 telnet 사용하기 Tip(feat. curl)</title>
      <link>https://poalim.tistory.com/47</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;telnet 명령어&lt;/b&gt;는 특정 서버에 접속할 수 있는 명령어이지만, 주로 서버의 포트 오픈 여부를 확인할 때 많이 사용된다.&lt;/p&gt;
&lt;pre id=&quot;code_1685723803545&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// telnet [IP] [PORT]
$ telnet 127.0.0.1 8080&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;하지만, 종종 telnet 설치가 안 되어 있는 경우에 포트 점검을 하고 싶은 경우가 있다.(telnet 활성화가 안 된 윈도우, telnet 미설치 VM, pod 등)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Tip&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;curl 명령어는 http 뿐 아니라 telnet 프로토콜도 허용이 된다.&lt;/b&gt; (curl은 웬만한 경우 설치가 되어 있다.)&lt;/p&gt;
&lt;pre id=&quot;code_1685724141871&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// curl -v telnet://[IP]:[PORT]
$ curl -v telnet://127.0.0.1:8080&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 curl 명령어를 사용해 telnet 접속을 했을 때 포트 오픈이 되어 있다면 해당 서버로 접속이 되고, 포트 오픈이 되어 있지 않다면 Failed to connection을 볼 수 있다.&lt;/p&gt;</description>
      <category>Programming/기타</category>
      <category>ACL</category>
      <category>cURL</category>
      <category>telnet</category>
      <category>연결불가</category>
      <category>연결확인</category>
      <category>포트연결</category>
      <author>aa_rong_blog</author>
      <guid isPermaLink="true">https://poalim.tistory.com/47</guid>
      <comments>https://poalim.tistory.com/47#entry47comment</comments>
      <pubDate>Sat, 3 Jun 2023 01:45:36 +0900</pubDate>
    </item>
    <item>
      <title>[Git] fork 후 작업하기</title>
      <link>https://poalim.tistory.com/46</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;fork 와 clone 의 차이점&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;fork&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fork 는 다른 사람의 repository 에 내가 어떤 부분을 수정하거나 추가하고 싶은 경우에&amp;nbsp;&lt;b&gt;해당 repository 를 내 계정의 repository 로 복제&lt;/b&gt;하는 기능이다. 이때, fork 해온 저장소는 원본 저장소와 연결되어 있어서 fetch, rebase 등을 통해 원본 저장소의 변경 사항을 내 저장소에 반영할 수 있다.&lt;br /&gt;이후 내 저장소에서 작업을 진행한 후&amp;nbsp;&lt;b&gt;변경 사항을 원본 저장소에 적용하고 싶으면 해당 저장소에 Pull Request(PR) 을 해야 한다. PR 이 원본 저장소의 관리자로부터 승인되면, 나의 변경 사항이 원본 저장소에 commit, merge 되어 반영된다.&amp;nbsp;&lt;/b&gt;PR 이 승인되기 전까지는 변경 사항이 fork 한 내 저장소에만 존재한다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;clone&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;clone 은 특정 원격 repository 를 내 로컬 작업공간에 복사하여 새로운 저장소를 만든다. 로컬 저장소는 원본 repository 를 remote 저장소 &lt;b&gt;origin&lt;/b&gt; 으로 가지고 있다. 이때, clone 한 개인은 권한이 없는 경우 해당 저장소로 push 하지 못한다. 권한이 있는 경우, push 할 경우 변경 내역이 원본 저장소로 바로 반영된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Fork 하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 먼저 fork 할 원본 저장소로 이동한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. fork 버튼을 클릭하면 내 계정에 forked 된 저장소가 생성된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;446&quot; data-origin-height=&quot;52&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Z2yqD/btryONVFOr3/RZkw4rb9bUJxWaVvlQM2cK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Z2yqD/btryONVFOr3/RZkw4rb9bUJxWaVvlQM2cK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Z2yqD/btryONVFOr3/RZkw4rb9bUJxWaVvlQM2cK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZ2yqD%2FbtryONVFOr3%2FRZkw4rb9bUJxWaVvlQM2cK%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;446&quot; height=&quot;52&quot; data-origin-width=&quot;446&quot; data-origin-height=&quot;52&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. &lt;b&gt;내 저장소&lt;/b&gt;에 생성된 repository 를 clone 하여 로컬 작업환경을 만든다.&lt;/p&gt;
&lt;pre id=&quot;code_1649439684808&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ git clone https://github.com/...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. Remote 설정&lt;br /&gt;clone 후에는&amp;nbsp;&lt;b&gt;origin&lt;/b&gt; 이라는 remote 가 생성되는데, 이건 내가 Fork 한 프로젝트이다. 여기서&amp;nbsp;&lt;b&gt;pull 로 변경 내역을 받으면, 원본 프로젝트에서 받는 게 아니라 Fork 된 내 프로젝트의 변경 내역을 받게 된다.&amp;nbsp;&lt;/b&gt;원본 저장소의 변경 내역을 파악하여 원 저자와의 중복 작업을 피하고 예전 소스를 수정하는 일이 없도록&amp;nbsp;&lt;b&gt;원본 저장소의 정보를 remote add 로 추가한다.&lt;/b&gt; (upstream 이라는 이름으로 등록하였다)&lt;/p&gt;
&lt;pre id=&quot;code_1649439650272&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ git remote add upstream http://github.com/...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 원본 저장소의 변경 내용을 가져오려면 pull, fetch 를 수행한다. pull 과 fetch 의 차이점은 fetch 는 merge 를 수행하지 않는다는 점이다.&lt;/p&gt;
&lt;pre id=&quot;code_1649439738368&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ git fetch upstream&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;Fork 한 프로젝트에서 진행한 작업을 원본 저장소에 반영하고 싶으면 아래 과정을 따르면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 로컬 저장소에서&amp;nbsp;&lt;b&gt;작업한 내용을 commit, push&amp;nbsp;&lt;/b&gt;한다. (이때, 변경 내역은 원본 저장소가 아닌, Fork 한 개인 저장소에 반영된다.)&lt;/p&gt;
&lt;pre id=&quot;code_1649439879684&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ git add .
$ git commit -m &quot;message&quot;
$ git push origin master&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 원본 저장소의 관리자에게 내 작업내용을 Pull Request 로 보낸다.&lt;br /&gt;push 를 하고 나서 내 계정의 Fork 된 repository 를 들어가보면 아래와 같은 버튼이 활성화되어 있을 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;670&quot; data-origin-height=&quot;88&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oDOXZ/btryO5Ppu29/kzQBKnaZSzaJS8127msJhk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oDOXZ/btryO5Ppu29/kzQBKnaZSzaJS8127msJhk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oDOXZ/btryO5Ppu29/kzQBKnaZSzaJS8127msJhk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoDOXZ%2FbtryO5Ppu29%2FkzQBKnaZSzaJS8127msJhk%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;670&quot; height=&quot;88&quot; data-origin-width=&quot;670&quot; data-origin-height=&quot;88&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;compare &amp;amp; pull request&lt;/b&gt; 버튼을 눌러 pull request 를 작성한다. (어떤 기능을 어떻게 수정했는지 자세하게 적어준다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. Reviewers 가 PR 을 검토한 후 review 를 달아주거나, 문제가 없고 좋은 코드라면 관리자 권한을 가진 사람이 merge 해주면 원본 저장소에 내 작업 내역이 반영된다.&lt;/p&gt;</description>
      <category>Programming/Git</category>
      <category>fork</category>
      <category>git clone</category>
      <category>git fork</category>
      <category>git pull request</category>
      <category>pull request</category>
      <author>aa_rong_blog</author>
      <guid isPermaLink="true">https://poalim.tistory.com/46</guid>
      <comments>https://poalim.tistory.com/46#entry46comment</comments>
      <pubDate>Sat, 9 Apr 2022 02:51:15 +0900</pubDate>
    </item>
    <item>
      <title>[Git] Git 으로 작업하기</title>
      <link>https://poalim.tistory.com/45</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;사용자 설정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Git 을 설치하고 사용자 정보를 설정해두면, Git 은 커밋할 때마다 이 정보를 사용한다. 한 번 커밋한 후에는 정보를 변경할 수 없다.&lt;/p&gt;
&lt;pre id=&quot;code_1649433519086&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ git config --global user.name &quot;사용자 이름&quot;
$ git config --global user.email &quot;사용자 이메일&quot;

// git 사용자 설정 확인
$ git config --list&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;--global 옵션&lt;/b&gt;은 한 컴퓨터 당 단 한 번만 하면 작업하는 &lt;b&gt;모든 프로젝트에 적용&lt;/b&gt;된다. 만약 프로젝트마다 다른 이름과 이메일 주소를 사용하고 싶으면 해당 프로젝트 내에서 --global 옵션을 빼고 위 명령을 실행한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Git 저장소 생성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작업의 종류에 따라 두 가지 경우가 있다. 1) 이미 존재하는 프로젝트를 받아오는 경우, 2) 새로 프로젝트를 시작하는 경우.&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. Github, Bitbucket 등에서 받아올 프로젝트로 이동한 뒤 Repository path 복사&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;912&quot; data-origin-height=&quot;339&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Wjx6d/btryP8YO9IF/iRj0fTt2rsxnHaw5V9yPB0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Wjx6d/btryP8YO9IF/iRj0fTt2rsxnHaw5V9yPB0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Wjx6d/btryP8YO9IF/iRj0fTt2rsxnHaw5V9yPB0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWjx6d%2FbtryP8YO9IF%2FiRj0fTt2rsxnHaw5V9yPB0%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;912&quot; height=&quot;339&quot; data-origin-width=&quot;912&quot; data-origin-height=&quot;339&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 복사한 Repository path 를 로컬에 Clone 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1649434477504&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ git clone repository-path&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;새로 프로젝트를 시작하는 경우&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Git 으로 관리하고 싶은 폴더로 이동 (빈 폴더이든, 이미 파일이 있는 폴더이든 상관 없다.)&lt;/p&gt;
&lt;pre id=&quot;code_1649434761967&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 폴더가 없다면 폴더 생성
$ mkdir new-git-directory

// 생성한 폴더로 이동
$ cd new-git-directory&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 저장소 초기화 (해당 폴더를 Git 이 관리하는 상태로 만들어준다.)&lt;/p&gt;
&lt;pre id=&quot;code_1649434951740&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ git init&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 최초 커밋하기&lt;br /&gt;git 은 파일 단위로 버전을 관리하기 때문에 빈 폴더(혹은 폴더들만 존재하는) 상태에서는 커밋을 할 수 없다. README 파일을 생성한 후 최초 커밋을 해준다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1649435537636&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// README.md 파일 생성
$ touch README.md

$ git add README.md
$ git commit -m &quot;first commit&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 원격 저장소 연결&lt;br /&gt;Github 에 원격 저장소를 만들어 준 후 위에서 로컬에 만든 저장소와 연결한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;248&quot; data-origin-height=&quot;227&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JcGu8/btryQR3cSEj/X5jr1DIJ11IXKTGeip04pk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JcGu8/btryQR3cSEj/X5jr1DIJ11IXKTGeip04pk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JcGu8/btryQR3cSEj/X5jr1DIJ11IXKTGeip04pk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJcGu8%2FbtryQR3cSEj%2FX5jr1DIJ11IXKTGeip04pk%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;248&quot; height=&quot;227&quot; data-origin-width=&quot;248&quot; data-origin-height=&quot;227&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Github 오른쪽 위 화면에서 &lt;b&gt;+ 버튼&lt;/b&gt;을 누르고 &lt;b&gt;New repository&lt;/b&gt; 를 클릭한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;733&quot; data-origin-height=&quot;827&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b61E73/btryRRu1rrO/mO9CL2LeMjk1zy96VHUf2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b61E73/btryRRu1rrO/mO9CL2LeMjk1zy96VHUf2k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b61E73/btryRRu1rrO/mO9CL2LeMjk1zy96VHUf2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb61E73%2FbtryRRu1rrO%2FmO9CL2LeMjk1zy96VHUf2k%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;733&quot; height=&quot;827&quot; data-origin-width=&quot;733&quot; data-origin-height=&quot;827&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성할 원격 저장소 정보를 입력한 후 &lt;b&gt;Create repository&lt;/b&gt; 를 클릭하면 친절하게 이후 단계를 설명해준다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;394&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oaj5A/btryO4JDaLq/BNUaGI7ARiFFvX4LbJMvrk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oaj5A/btryO4JDaLq/BNUaGI7ARiFFvX4LbJMvrk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oaj5A/btryO4JDaLq/BNUaGI7ARiFFvX4LbJMvrk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Foaj5A%2FbtryO4JDaLq%2FBNUaGI7ARiFFvX4LbJMvrk%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;700&quot; height=&quot;394&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;394&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 이미 로컬에 저장소를 만들었기 때문에 두 번째(...or push an existing repository from the command line) 에 있는 명령어들을 그대로 넣어주면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1649436185118&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 로컬 저장소와 원격 저장소를 연결
$ git remote add origin repository-path

// -M 옵션은 branch 이름을 변경해주는 옵션이다.
// -M &amp;lt;oldBranch&amp;gt; &amp;lt;newBranch&amp;gt;
// &amp;lt;oldBranch&amp;gt; 를 생략할 경우 현재 branch 이름을 newBranch 로 변경한다.
// master -&amp;gt; main 으로 브랜치 명이 변경된다.
// 생략하고 master 를 그대로 사용해도 된다.
$ git branch -M main

// -u 옵션은 현재 branch 를 origin 이라는 원격 저장소의 main 브랜치로 연결해준다.
// 한 번 연결해두면 이후에는 git push, git pull 명령어 만으로 해당 브랜치로 merge 가 가능하게 된다.
$ git push -u origin main&lt;/code&gt;&lt;/pre&gt;

&lt;h2 data-ke-size=&quot;size26&quot;&gt;Branch 설정&lt;/h2&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;/ul&gt;
&lt;pre id=&quot;code_1649436974012&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ git branch	// 로컬 저장소 branch 확인
$ git branch -r // 원격 저장소 branch 확인
$ git branch -a // 로컬+원격 저장소 branch 확인&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원격 저장소에 여러 branch 가 있어도, 로컬에 처음 clone 한 후에는 원격 저장소의 branch 는 받아지지 않고 기존에 있던 master(or main) 하나만 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때, 만약 원격 저장소의 branch 를 로컬로 가져와 작업하고 싶다면 아래와 같이 하면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1649437113882&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 원격 저장소에 있는 branch 와 동일한 이름의 branch 를 로컬에 생성하고,
// 생성한 로컬 branch 를 원격 branch 와 동기화 한다.
// 이후 새로 생성한 branch 로 checkout 한다.
$ git checkout -t origin/branchName&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 branch 의 이름을 변경하여 가져오고 시다면 &lt;b&gt;$ git checkout -b [생성할 branch 이름] [원격 저장소의 branch 이름]&lt;/b&gt; 처럼 사용하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;※&amp;nbsp;&lt;b&gt;$ git checkout [원격 저장소의 branch 이름]&lt;/b&gt; : 아무런 옵션 없이 원격 저장소의 branch 를 checkout 하면 'detached HEAD' 상태가 되어 소스를 보고 변경할 수 있지만, 변경 사항들을 commit/push 는 할 수 없다. 또한, 다른 branch 로 checkout 하면 변경 사항들은 모두 사라진다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;새로운 작업을 시작한다면, 로컬에서 새로운 branch 를 생성/작업한 후 원격 저장소로 해당 branch 를 푸시하면 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1649437557853&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// branch 생성 및 checkout
$ git checkout -b &amp;lt;branchName&amp;gt;

// 원격 저장소에 branch 생성 및 로컬 branch 와 연결
$ git push origin &amp;lt;branchName&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;기타 자주 사용하는 명령어&lt;/h2&gt;
&lt;pre id=&quot;code_1649438048298&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 원격 저장소에 변경 내역이 생겼을 경우 변경 내역 로컬 저장소에 받기
$ git pull origin &amp;lt;branchName&amp;gt;

// git 상태 확인 (unstaged, modified 파일 등)
$ git status

// 로컬에서 변경한 내역을 git 이 추적할 수 있는 staging area 에 추가
$ git add &amp;lt;fileName&amp;gt;
$ git add *	// 모든 unstaged/modified 파일을 추가
$ git add .	// 모든 unstaged/modified 파일을 추가

// staged 된 파일을 다시 unstaged 상태로 변경
$ git restore --staged &amp;lt;fileName&amp;gt;

// staging area 에 있는 변경 내역들을 commit
$ git commit -m &quot;commit message&quot;

// commit log 확인
$ git log

// commit 내역을 원격 저장소에 푸시
$ git push orgin &amp;lt;branchName&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1649438212251&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// git log 분석
// 특정 시간(since ~ before) 동안 선택된 branch 의 특정 사용자의 commit 정보를 출력한다.
$ git log --pretty=format: &quot;%h - %an, %ar : %s&quot; --since=&quot;2022-01-01&quot; --before=&quot;2022-03-25&quot; --no-merges --author=&quot;poarlim&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Programming/Git</category>
      <category>Git</category>
      <category>git branch</category>
      <category>GitHub</category>
      <author>aa_rong_blog</author>
      <guid isPermaLink="true">https://poalim.tistory.com/45</guid>
      <comments>https://poalim.tistory.com/45#entry45comment</comments>
      <pubDate>Sat, 9 Apr 2022 02:17:58 +0900</pubDate>
    </item>
    <item>
      <title>[Java] List 에서 ObjectUtils.isEmpty() 를 이용한 null 체크(isEmpty(), != null)</title>
      <link>https://poalim.tistory.com/44</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;List 를 사용하여 데이터를 조회하다 보면, 조회된 데이터가 없을 경우 NullPointerException 등이 터질 수 있기 때문에 null 체크를 해주어야 합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. List 가 null 일 경우&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인스턴스 자체가 생성되지 않았을 경우 List 는 null 값을 가진다. null 인 리스트를 조회하려고 하면, &lt;span style=&quot;color: #ee2323;&quot;&gt;NullPointerException&lt;/span&gt; 이 터진다. null 체크는 &quot;list == null&quot; 로 할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1648799109573&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if(list != null){
	// task
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드를 추가하면, list 가 null 이 아닌 경우에만 작업을 수행한다. 다만, 여전히 문제가 있다. 데이터를 받아오는 과정에서 list 에 null 이 아닌 빈 값이 저장되는 경우 위의 null 체크에 걸리지 않는다. (list == [])&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. List 가 빈 값인 경우(list == [])&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터를 조회하여 List 에 넣어주는 경우 List 에 [] 빈 값이 들어가는 경우가 있다. 이 경우 java.util.List 에서 제공하는 &lt;b&gt;isEmpty()&lt;/b&gt; 메소드를 사용하여 체크할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1648799724766&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if(list != null &amp;amp;&amp;amp; !list.isEmpty()){
	// task
}&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;이 경우, isEmpty() 전에 null 체크를 선행해주어야 한다. null 인 List 에 isEmpty() 메소드를 수행하면 &lt;span style=&quot;color: #ee2323;&quot;&gt;NullPointerException&lt;/span&gt; 이 발생하기 때문이다. (마찬가지로, size() 메소드를 호출하는 경우에도 List 가 null 경우 &lt;span style=&quot;color: #ee2323;&quot;&gt;NullPointerException&lt;/span&gt; 이 발생하기 때문에 null 체크가 필요하다.)&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. ObjectUtils.isEmpty()&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;org.springframework.util 라이브러리에서 제공하는 ObjectUtils.isEmpty() 를 사용하면 null 체크와 빈 문자열 체크를 동시에 할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1648800731066&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if(ObjectUtils.isEmpty(list)){
	// task
}&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;코드를 살펴보면 내부적으로 null 체크와 isEmpty() 를 동시에 수행한다.&lt;/p&gt;
&lt;pre id=&quot;code_1648800079744&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ObjectUtils
public static boolean isEmpty(@Nullable Object obj) {
    if (obj == null) {
        return true;
    } else if (obj instanceof Optional) {
        return !((Optional)obj).isPresent();
    } else if (obj instanceof CharSequence) {
        return ((CharSequence)obj).length() == 0;
    } else if (obj.getClass().isArray()) {
        return Array.getLength(obj) == 0;
    } else if (obj instanceof Collection) {
        return ((Collection)obj).isEmpty();
    } else {
        return obj instanceof Map ? ((Map)obj).isEmpty() : false;
    }
}&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;Collection 에 한정하여 사용할 수 있는 CollectionUtils.isEmpty() 도 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1648800307803&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// CollectionUtils
public static boolean isEmpty(@Nullable Collection&amp;lt;?&amp;gt; collection) {
    return collection == null || collection.isEmpty();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내부적으로 null, isEmpty() 를 수행하는 것은 동일하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;span style=&quot;color: #ee2323;&quot;&gt;NullPointerException&lt;/span&gt; 을 피하기 위해서 CollectionUtils.isEmpty(), ObjectUtils.isEmpty() 을 사용하는 것이 가장 좋아보인다.&lt;/p&gt;</description>
      <category>Programming/Java</category>
      <category>CollectionUtils.isEmpty</category>
      <category>isEmpty()</category>
      <category>java</category>
      <category>null</category>
      <category>null체크</category>
      <category>ObjectUtils.isEmpty</category>
      <category>자바</category>
      <author>aa_rong_blog</author>
      <guid isPermaLink="true">https://poalim.tistory.com/44</guid>
      <comments>https://poalim.tistory.com/44#entry44comment</comments>
      <pubDate>Fri, 1 Apr 2022 17:10:52 +0900</pubDate>
    </item>
    <item>
      <title>[알고리즘] 탐욕적 기법(Greedy Algorithm)</title>
      <link>https://poalim.tistory.com/43</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 탐욕적 기법(Greedy Algorithm)이란?&lt;/h2&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2021-11-01 오후 2.11.03.png&quot; data-origin-width=&quot;516&quot; data-origin-height=&quot;474&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wYCsG/btrjnIBoKID/zMB8Prk5JQWexy6XrbJ6z0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wYCsG/btrjnIBoKID/zMB8Prk5JQWexy6XrbJ6z0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wYCsG/btrjnIBoKID/zMB8Prk5JQWexy6XrbJ6z0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwYCsG%2FbtrjnIBoKID%2FzMB8Prk5JQWexy6XrbJ6z0%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;247&quot; height=&quot;227&quot; data-filename=&quot;스크린샷 2021-11-01 오후 2.11.03.png&quot; data-origin-width=&quot;516&quot; data-origin-height=&quot;474&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서울에서 부산까지 최단거리로 가는 방법은 서울-&amp;gt;대전-&amp;gt;부산의 경로로 4.5km 이지만, 이 상황에 탐욕적 기법을 적용하면 알고리즘은 각 단계에서 최적해, 즉 가장 가까이 있는 도시로 이동하여 서울-&amp;gt;인천-&amp;gt;대전-&amp;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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2021-11-01 오후 2.23.48.png&quot; data-origin-width=&quot;606&quot; data-origin-height=&quot;52&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dpMouW/btrjCSvg8DI/HsCKJYOkR55OwSnKnK4FF0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dpMouW/btrjCSvg8DI/HsCKJYOkR55OwSnKnK4FF0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dpMouW/btrjCSvg8DI/HsCKJYOkR55OwSnKnK4FF0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdpMouW%2FbtrjCSvg8DI%2FHsCKJYOkR55OwSnKnK4FF0%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;478&quot; height=&quot;41&quot; data-filename=&quot;스크린샷 2021-11-01 오후 2.23.48.png&quot; data-origin-width=&quot;606&quot; data-origin-height=&quot;52&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것이 가능한 이유는 모든 동전은 보다 단위가 작은 동전 액수의 배수이기 때문이다. 따라서 큰 금액의 동전을 더 작은 동전으로 교체하면 동전 개수가 반드시 늘어난다. 예를 들어 거스름돈이 50원일 때 30원,25원,10원 단위의 동전이 있다면 30+10+10 보다 25+25 의 경우가 더 최적해가 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 적용&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;탐욕적 알고리즘을 적용하기 위해서는 문제가 두 가지 조건을 만족해야 한다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;탐욕 선택 속성(Greedy Choice Property)&lt;/li&gt;
&lt;li&gt;최적 부분 구조(Optimal Substructure)&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1 번은 &lt;b&gt;이전의 선택이 이후에 영향을 주지 않아야 함&lt;/b&gt;을 의미하고, 2 번은 &lt;b&gt;부분 문제의 최적해가 전체 문제에도 그대로 적용&lt;/b&gt;될 수 있어야 한다는 것을 의미한다. 또한 이는 수학적 증명이 동반되어야 하지만, 일반적으로 이러한 증명을 해 내기란 쉬운 일이 아니다. 하나의 예시를 보자.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;N 개의 도시락이 있고, 전자레인지는 단 한 대만 존재하며 한 번에 하나의 도시락만 데울 수 있으며, 각 도시락은 고유의 조리시간과 먹는 시간이 정해져 있고 데운 도시락은 바로 누군가가 먹는다고 할 때, 도시락을 적절한 순서로 조리하여 모든 도시락을 다 먹는 데 걸리는 시간이 최소가 되게 하시오.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 문제를 해결하기 위해 임의의 도시락 1, 2 를 선택하고 각각을 데우는 데 걸리는 시간을 C1, C2 이라고 하고, 먹는데 걸리는 시간은 E1, E2 라고 하자.&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;E1 &amp;gt;= E2 인 경우
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;1 을 먼저 데울 경우 : C1 + C2 + max(E2, E1-C2)&lt;/li&gt;
&lt;li&gt;2 을 먼저 데울 경우 : C1 + C2 + max(E1, E2-C1) = C1 + C2 + E1&lt;/li&gt;
&lt;li&gt;위의 두 경우에서 1 을 먼저 데우는 경우에서 E2, E1-C2 중 어떤 값이 선택 되어도&amp;nbsp; E1 보다 작으므로 1을 먼저 데우는 경우가 항상 더 적은 시간이 걸린다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;E1 &amp;lt;= E2 인 경우
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;1 을 먼저 데울 경우 : C1 + C2 + max(E2, E1-C2) = C1 + C2 + E2&lt;/li&gt;
&lt;li&gt;2 을 먼저 데울 경우 : C1 + C2 + max(E1, E2-C1)&lt;/li&gt;
&lt;li&gt;마찬가지로, E1, E2-C1 두 값 모두 E2 모다 작기 때문에 항상 2을 먼저 데우는 경우가 최적의 경우의 수이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 수학적 증명을 통해 항상 먹는 데 시간이 오래 걸리는 도시락을 먼저 데우는 것이 최적의 것임을 알 수 있다. 하지만 일반적으로 이런 증명을 찾아내기란 쉽지 않기 때문에, 탐욕적 기법은 주로 &lt;b&gt;감에 의지하거나 테스트 코드를 작성하는 방식&lt;/b&gt;으로 이루어 지는 경우가 많다. 이 경우, &lt;b&gt;반례를 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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Programming/Algorithms</category>
      <category>그리디</category>
      <category>알고리즘</category>
      <category>탐욕적기법</category>
      <author>aa_rong_blog</author>
      <guid isPermaLink="true">https://poalim.tistory.com/43</guid>
      <comments>https://poalim.tistory.com/43#entry43comment</comments>
      <pubDate>Mon, 1 Nov 2021 15:30:21 +0900</pubDate>
    </item>
    <item>
      <title>[Programmers] 입국심사 - 이분탐색(Binary Search)</title>
      <link>https://poalim.tistory.com/42</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://programmers.co.kr/learn/courses/30/lessons/43238&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://programmers.co.kr/learn/courses/30/lessons/43238&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1633091751143&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;n명이 입국심사를 위해 줄을 서서 기다리고 있습니다. 각 입국심사대에 있는 심사관마다 심사하는데 걸리는 시간은 다릅니다. 처음에 모든 심사대는 비어있습니다. 한 심사대에서는 동시에 한 &quot; data-og-host=&quot;programmers.co.kr&quot; data-og-source-url=&quot;https://programmers.co.kr/learn/courses/30/lessons/43238&quot; data-og-url=&quot;https://programmers.co.kr/learn/courses/30/lessons/43238&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/s0MFZ/hyLN93JHaY/g8XoHZjLPJqlUfuXIyUdDk/img.jpg?width=626&amp;amp;height=626&amp;amp;face=0_0_626_626,https://scrap.kakaocdn.net/dn/dQ6xbk/hyLN5tuP5m/k63asYGnKhuTI0S83BW6ik/img.jpg?width=626&amp;amp;height=626&amp;amp;face=0_0_626_626&quot;&gt;&lt;a href=&quot;https://programmers.co.kr/learn/courses/30/lessons/43238&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://programmers.co.kr/learn/courses/30/lessons/43238&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/s0MFZ/hyLN93JHaY/g8XoHZjLPJqlUfuXIyUdDk/img.jpg?width=626&amp;amp;height=626&amp;amp;face=0_0_626_626,https://scrap.kakaocdn.net/dn/dQ6xbk/hyLN5tuP5m/k63asYGnKhuTI0S83BW6ik/img.jpg?width=626&amp;amp;height=626&amp;amp;face=0_0_626_626');&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;n명이 입국심사를 위해 줄을 서서 기다리고 있습니다. 각 입국심사대에 있는 심사관마다 심사하는데 걸리는 시간은 다릅니다. 처음에 모든 심사대는 비어있습니다. 한 심사대에서는 동시에 한&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;programmers.co.kr&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;h3 data-ke-size=&quot;size23&quot;&gt;접근&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 문제의 접근법부터 생각해야 한다. 이 문제를 이분탐색으로 풀게 된 이유는 문제의 범위를 보면 간단히 알 수 있다. 입국 심사를 기다리는 사람이 최대 1,000,000,000 명, 한 명을 심사하는 데 걸리는 시간이 최대 1,000,000,000 분이므로 O(n) 의 시간복잡도 마저 부담스러운 값이 나온다. 이러한 &lt;b&gt;매우&lt;/b&gt;&amp;nbsp;&lt;b&gt;큰 범위가 주어지면, O(logn) 의 시간복잡도를 가지는 이분탐색을 우선적으로 고려&lt;/b&gt;해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후에 생각해야 할 것은 &lt;b&gt;무엇을 기준으로 이분탐색을 할 것인가&lt;/b&gt;를 생각해야 한다. 정답인 answer 가 시간의 최솟값을 요구하였으므로 시간을 기준으로 이분탐색을 진행하면 된다는 생각을 할 수 있다. 이제 진행해보자.&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;최소값 : 가장 적은 시간이 걸리는 경우의 수는 손님이 단 1 명 존재하고, 이 사람이 심사를 받는 데 걸리는 시간이 1&amp;nbsp; 분 걸리는 경우이다. (minTime = 1)&lt;/li&gt;
&lt;li&gt;최대값 : 가장 오랜 시간이 걸리는 경우의 수는 손님 n 명이 모두 심사하는데 가장 오래 걸리는 심사관에게 심사를 받는 경우이다. (maxTime = n * times[times.size() - 1], times 는 정렬된 상태여야 한다.)&lt;/li&gt;
&lt;li&gt;추가로 내가 찾으려는 특정 시간에 심사할 수 있는 사람 수의 최대값을 구해야 한다. 이 값이 n 보다 크거나 같을 때 해당 시간은 문제의 조건을 만족하고, 이 시간들 중 최소값이 정답이 된다.&lt;/li&gt;
&lt;/ul&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;pre id=&quot;code_1633140119213&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;string&amp;gt;
#include &amp;lt;vector&amp;gt;
#include &amp;lt;algorithm&amp;gt;
using namespace std;

long long solution(int n, vector&amp;lt;int&amp;gt; times) {
    sort(times.begin(), times.end());
    long long minTime = 1;
    long long maxTime = (long long)times[times.size() - 1] * n;
    long long avgTime;
    long long answer = 0;
    
    while(minTime &amp;lt;= maxTime){
        avgTime = (minTime + maxTime) / 2;
        
        long long cnt = 0;
        for(int time : times){
            cnt += avgTime / time;
        }
        
        if(cnt &amp;lt; n) minTime = avgTime + 1;
        else maxTime = avgTime - 1;
    }
    
    return minTime;
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Programming/Algorithms</category>
      <category>알고리즘</category>
      <category>이분탐색</category>
      <author>aa_rong_blog</author>
      <guid isPermaLink="true">https://poalim.tistory.com/42</guid>
      <comments>https://poalim.tistory.com/42#entry42comment</comments>
      <pubDate>Sat, 2 Oct 2021 11:02:45 +0900</pubDate>
    </item>
    <item>
      <title>[알고리즘] 이분 탐색(Binary Search)</title>
      <link>https://poalim.tistory.com/41</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이분 탐색&lt;/b&gt;이란 주어진 데이터 중에서 원하는 데이터를 찾는 탐색 기법 중 하나로, 전체 범위를 두 부분으로 나누는 과정을 반복하여 목표값을 찾아내는 기법이다. 간단하게 예를 들면, 레크레이션 등에서 많이 하는 업다운(Up, Down) 게임을 생각하면 된다. 1~100 숫자 중 사회자가 생각한 숫자를 맞출 때 중간값인 50을 처음 부르고 이후 사회자의 Up, Down 대답에 따라 75, 25 등 나머지 범위의 중간값을 계속 부르는 방식으로 진행된다.&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;초기 설정 (left = 최소 index 값, right = 최대 index 값)&lt;/li&gt;
&lt;li&gt;mid 값 설정 (left : 최소 index 값, right : 최대 index 값, mid = (left+right)/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;mid 값과 dest(목표값) 비교
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;mid == dest : 탐색 완료 (종료)&lt;/li&gt;
&lt;li&gt;mid &amp;gt; dest : right = mid - 1&lt;/li&gt;
&lt;li&gt;mid &amp;lt; dest : left = mid + 1&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;left &amp;gt; right 가 될 때까지 2 ~ 4 과정 반복&lt;/li&gt;
&lt;/ol&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;pre id=&quot;code_1633074475991&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int binarySearch(vector&amp;lt;int&amp;gt; numbers, int dest){
    int left, right, mid;

    left = 0;
    right = numbers.size() - 1;
    
    while(left &amp;lt;= right){
        mid = (left + right) / 2
        
        if(numbers[mid] == dest) return mid;
        else if(numbers[mid] &amp;gt; dest) right = mid - 1;
        else left = mid + 1;
    }

    return -1;	// 탐색 실패
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Programming/Algorithms</category>
      <category>Binary Search</category>
      <category>알고리즘</category>
      <category>이분탐색</category>
      <author>aa_rong_blog</author>
      <guid isPermaLink="true">https://poalim.tistory.com/41</guid>
      <comments>https://poalim.tistory.com/41#entry41comment</comments>
      <pubDate>Fri, 1 Oct 2021 16:49:07 +0900</pubDate>
    </item>
    <item>
      <title>[BOJ] 백준 11726번 - 2&amp;times;n 타일링</title>
      <link>https://poalim.tistory.com/39</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/11726&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.acmicpc.net/problem/11726&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1624342387440&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;11726번: 2&amp;times;n 타일링&quot; data-og-description=&quot;2&amp;times;n 크기의 직사각형을 1&amp;times;2, 2&amp;times;1 타일로 채우는 방법의 수를 구하는 프로그램을 작성하시오. 아래 그림은 2&amp;times;5 크기의 직사각형을 채운 한 가지 방법의 예이다.&quot; data-og-host=&quot;www.acmicpc.net&quot; data-og-source-url=&quot;https://www.acmicpc.net/problem/11726&quot; data-og-url=&quot;https://www.acmicpc.net/problem/11726&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dA13p7/hyKDVzwsWZ/D7U7j3Bjp3QOiWIqGdm0Bk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/b3VUin/hyKFsbqDdS/ZWMIaGfRuXskYSHo57CYR1/img.png?width=1381&amp;amp;height=554&amp;amp;face=0_0_1381_554&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/11726&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.acmicpc.net/problem/11726&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dA13p7/hyKDVzwsWZ/D7U7j3Bjp3QOiWIqGdm0Bk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/b3VUin/hyKFsbqDdS/ZWMIaGfRuXskYSHo57CYR1/img.png?width=1381&amp;amp;height=554&amp;amp;face=0_0_1381_554');&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;11726번: 2&amp;times;n 타일링&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;2&amp;times;n 크기의 직사각형을 1&amp;times;2, 2&amp;times;1 타일로 채우는 방법의 수를 구하는 프로그램을 작성하시오. 아래 그림은 2&amp;times;5 크기의 직사각형을 채운 한 가지 방법의 예이다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.acmicpc.net&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;대표적인 DP 문제이다.&lt;/p&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 크기의 직사각형을 1*2, 2*1 두 종류의 타일로 채울 때, 모든 경우는 2가지로 나눌 수 있다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;가장 왼쪽 타일이 2*1 타일로 시작하는 경우&lt;/li&gt;
&lt;li&gt;가장 왼쪽 타일이 1*2 타일로 시작하는 경우 -&amp;gt; 1*2 타일은 위아래로 붙어서 2*2 형태로만 존재할 수 있다.&lt;/li&gt;
&lt;/ol&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종류만 존재하므로 가장 왼쪽 타일은 2*1타일 혹은 1*2타일이어야만 한다. 1번 경우를 보면 가장 왼쪽에 위치하는 2*1 타일 하나를 제외하고 보면 오른쪽 부분은 2 * (n-1) 크기의 직사각형이 된다. 이 직사각형을 채우는 방법의 수는 f(n-1) 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2번 경우는 1*2 타일은 2개씩 붙어서 2*2 형태로 존재해야만 하므로 가장 왼쪽의 2*2 모양의 타일을 제외하고 보면 오른쪽 부분은 2 * (n-2) 크기의 직사각형이고 이를 채우는 방법의 수는 f(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;f(1) = 1&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;f(2) = 2&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;f(n) = f(n-1) + f(n-2). (n&amp;gt;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;이는 피보나치 수열을 이루고, 점화식을 이루는 문제는 DP 를 통해 해결 가능하다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1624343294561&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
using namespace std;

int dp[1001] = {0};
int Tiling (int n);

int main (){
    int n;
    cin&amp;gt;&amp;gt;n;
    Tiling(n);

    cout&amp;lt;&amp;lt;dp[n];

    return 0;
}

int Tiling (int n){
    if (dp[n] != 0) return dp[n];
    if (n == 1) dp[1] = 1;
    else if (n == 2) dp[2] = 2;
    else dp[n] = (Tiling (n - 1) + Tiling (n - 2)) % 10007;

    return dp[n];
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Programming/Algorithms</category>
      <author>aa_rong_blog</author>
      <guid isPermaLink="true">https://poalim.tistory.com/39</guid>
      <comments>https://poalim.tistory.com/39#entry39comment</comments>
      <pubDate>Tue, 22 Jun 2021 15:28:57 +0900</pubDate>
    </item>
    <item>
      <title>[C++] getline() - 한 줄 입력받기</title>
      <link>https://poalim.tistory.com/38</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;std::getline() 함수는 입력스트림에서 한 줄을 읽어 저장한다. 아래는 여러 줄의 문자열을 그대로 출력하는 코드이다.&lt;/p&gt;
&lt;pre id=&quot;code_1624327564415&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
using namespace std;

int main (){
    string s;
    while (getline(cin, s)){
        cout&amp;lt;&amp;lt;s&amp;lt;&amp;lt;endl;
    }
    return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;getline 함수는 입력받은 문자열을 그대로 리턴하므로 정상적인 입력일 경우 0 이 아닌 값을 반환하여 while 루프의 조건으로 지정할 수 있다.&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;iostream 의 getline 도 있고 string 의 getline 도 있으므로 정리해두자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 포스트는 string 의 getline 에 대한 설명이다.&lt;/p&gt;</description>
      <category>Programming/C++</category>
      <author>aa_rong_blog</author>
      <guid isPermaLink="true">https://poalim.tistory.com/38</guid>
      <comments>https://poalim.tistory.com/38#entry38comment</comments>
      <pubDate>Tue, 22 Jun 2021 11:21:49 +0900</pubDate>
    </item>
    <item>
      <title>[C++] EOF 입력받기</title>
      <link>https://poalim.tistory.com/37</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;C 에서는 eof 입력을 보통 다음과 같이 처리했었다.&lt;/p&gt;
&lt;pre id=&quot;code_1624324999948&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;while (scanf(&quot;%d %d&quot;, &amp;amp;a, &amp;amp;b) != EOF){ /* code here */ }&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;C++ 에서는 cin 객체와 &amp;gt;&amp;gt; operator 로 입력을 받을 때는 eof 의 입력을 다른 방식으로 처리해 주어야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1624323046123&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;while (std::cin &amp;gt;&amp;gt; a &amp;gt;&amp;gt; b) { /* code here */ }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 입력 받으면 eof 를 입력받을 시 반복문이 종료된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cin 은 실제 입력을 수행하는 클래스인 istream 클래스 객체이고, &amp;gt;&amp;gt; operator 는 istream 클래스에 정의되어 있는 연산자이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;gt;&amp;gt; operator 는 istream 객체의 값을 추출하여 변수로 넘겨주는 역할을 하고 &lt;b&gt;호출한 자신을 반환&lt;/b&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;cin &amp;gt;&amp;gt; a&amp;nbsp; &amp;nbsp; -&amp;gt; a 에 1 저장, cin 객체는 2 값을 가짐, &amp;gt;&amp;gt; 은 cin 을 반환.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cin &amp;gt;&amp;gt; b.&amp;nbsp; &amp;nbsp;-&amp;gt; b 에 2 저장, cin 객체는 NULL 값을 가짐. &amp;gt;&amp;gt; 은 cin 을 반환.&lt;/p&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;C++ 에서 eof 를 입력받는 방식은 여러 가지가 있으며 아래와 같은 방식도 가능하다.&lt;/p&gt;
&lt;pre id=&quot;code_1624324935996&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;while (!std::cin.eof ()){
    std::cin&amp;gt;&amp;gt;a&amp;gt;&amp;gt;b;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Programming/C++</category>
      <author>aa_rong_blog</author>
      <guid isPermaLink="true">https://poalim.tistory.com/37</guid>
      <comments>https://poalim.tistory.com/37#entry37comment</comments>
      <pubDate>Tue, 22 Jun 2021 10:22:59 +0900</pubDate>
    </item>
    <item>
      <title>[Pintos] Project 1 : Thread(스레드) - Advanced Scheduler (mlfqs)</title>
      <link>https://poalim.tistory.com/36</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;자 이제 스레드의 마지막 미션인 Advanced Scheduler 를 구현해보도록 하자. Advanced Scheduler 는 &lt;b&gt;Pintos 도큐먼트 Appendix B 에 기록되어 있는 4.4BSD Scheduler&lt;/b&gt; 와 같은 맥락의 스케줄러를 구현하도록 요구한다. 4.4BSD Scheduler 는 &lt;b&gt;Multi-Level Feedback Queue Scheduler&lt;/b&gt; 의 구성이며, 이를 줄여서 pintos 의 코드에서는 &lt;b&gt;mlfqs&lt;/b&gt; 로 나타나 있다. 4.4BSD 스케줄러의 상세한 내용은 전에 다룬 포스트가 있으니 자세한 이해를 원한다면 아래 링크에서 읽어보길 바란다. (recent_cpu, load_avg 값의 계산식을 분석하는 부분이 매우 흥미롭다.)&lt;/p&gt;
&lt;figure id=&quot;og_1620789789490&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Pintos] 4.4BSD 스케줄러(4.4BSD Scheduler) - niceness, priority, recent_cpu, load_avg, fixed-point real arithmetic&quot; data-og-description=&quot;멀티스레드 프로그램에서는 구조가 상이한 여러 스레드들이 함께 존재한다. 어떤 스레드는 다수의 I/O 요청을 처리하지만 복잡한 연산 작업은 없을 수 있으며, 어떤 스레드는 I/O 요청 없이 다수&quot; data-og-host=&quot;poalim.tistory.com&quot; data-og-source-url=&quot;https://poalim.tistory.com/25&quot; data-og-url=&quot;https://poalim.tistory.com/25&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/92Y7J/hyKbq62vrV/I8SsvFc017QAxCOYU3ETTk/img.png?width=176&amp;amp;height=17&amp;amp;face=0_0_176_17,https://scrap.kakaocdn.net/dn/vpvCY/hyKbr5WM6c/6SLxDYVJk7CZcrQ74bYy71/img.png?width=176&amp;amp;height=17&amp;amp;face=0_0_176_17&quot;&gt;&lt;a href=&quot;https://poalim.tistory.com/25&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://poalim.tistory.com/25&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/92Y7J/hyKbq62vrV/I8SsvFc017QAxCOYU3ETTk/img.png?width=176&amp;amp;height=17&amp;amp;face=0_0_176_17,https://scrap.kakaocdn.net/dn/vpvCY/hyKbr5WM6c/6SLxDYVJk7CZcrQ74bYy71/img.png?width=176&amp;amp;height=17&amp;amp;face=0_0_176_17');&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;[Pintos] 4.4BSD 스케줄러(4.4BSD Scheduler) - niceness, priority, recent_cpu, load_avg, fixed-point real arithmetic&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;멀티스레드 프로그램에서는 구조가 상이한 여러 스레드들이 함께 존재한다. 어떤 스레드는 다수의 I/O 요청을 처리하지만 복잡한 연산 작업은 없을 수 있으며, 어떤 스레드는 I/O 요청 없이 다수&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;poalim.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;h2 data-ke-size=&quot;size26&quot;&gt;Why mlfqs?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전까지 구현한 priority scheduler 는 그 실행을 오직 priority 에만 맡기기 때문에 priority 가 낮은 스레드들은 CPU 를 점유하기가 매우 어렵고 이로 인하여 평균반응시간(Average response time) 은 급격히 커질 가능성이 있다. 물론 priority donation 을 통하여 priority 가 높은 스레드들이 실행되는 데 필요한 스레드는 priority 의 보정을 받지만, 이마저도 받지 못하는 스레드가 존재할 가능성도 매우 크다. &lt;b&gt;Advanced Scheduler&lt;/b&gt; 는 이러한 문제를 해결하고 average response time 을 줄이기 위해 &lt;b&gt;multi-level feedback queue 스케줄링 방식&lt;/b&gt;을 도입한다. 이 스케줄링 방식의 특징은 크게 두 가지가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Priority 에 따라 여러 개의 Ready Queue 가 존재한다. (Multi-level)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Priority 는 실시간으로 조절한다. (Feedback)&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자는 이 중에서 &lt;b&gt;2번의 특징에 집중&lt;/b&gt;하여 advanced scheduler 를 구현해 보려고 한다. 1번 특징에 따르면, pintos 의 priority 는 0~63 의 범위를 가지므로 64개의 ready queue 를 만들고 이 중 가장 높은 priority 의 ready queue 안에서 round-robin 방식의 스케줄링을 하는 것이지만, 이미 앞선 과제들을 통하여 구현한 스케줄러에서 priority 에 따른 실행은 만족시켰으므로 굳이 1번 특징은 새로 도입하지 않고 2번 특징에 따라 각 스레드의 priority 를 조정하면 알아서 가장 높은 priority 를 가진 스레드가 실행될 것이다.&lt;/p&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;h2 data-ke-size=&quot;size26&quot;&gt;작동방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pintos document 에서는 일반 priority scheduler 와 advanced scheduler 중 무엇을 적용할 지 사용자가 선택하도록 요구한다. pintos 를 실행시킬 때 &lt;b&gt;'-mlfqs' 옵션&lt;/b&gt;을 넣어주면 advanced scheduler 를 적용하여 kernel 을 작동시키게 된다. 잠깐 살펴보고 가자.&lt;/p&gt;
&lt;pre id=&quot;code_1620791766103&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* threads/init.c */
static char **
parse_options (char **argv) 
{
    ...
    else if (!strcmp (name, &quot;-mlfqs&quot;))
      thread_mlfqs = true;
    ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pintos 의 작성시 '-mlfqs' 옵션이 들어오면 thread_mlfqs 가 true 값을 가지도록 구현되어 있다. 이 값은 &amp;lt;thread.h&amp;gt; 에 정의되어 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1620791901905&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* threads/thread.h */
extern bool thread_mlfqs;&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;이제 thread_mlfqs 가 true 일 때는 advanced scheduler 를, false 일 때는 기존 스케줄러를 사용하도록 구현하면 된다.&lt;/p&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;h2 data-ke-size=&quot;size26&quot;&gt;Niceness, Priority, Recent_cpu, Load_avg&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;priority 를 계산하는 데 필요한 각 값들에 대한 자세한 설명은 맨 위에서 언급한 4.4BSD Scheduler 포스트를 참고하도록 하고, 여기서는 간단하게 구현에 필요한 특징들만을 짚고 넘어가도록 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선적으로 알아두어야 하는 특징은 &lt;b&gt;niceness, priority, recent_cpu 는 각 스레드별로 존재하는 고유 값이고, load_avg 는 시스템에 하나의 값으로 존재&lt;/b&gt;한다는 것과, &lt;b&gt;niceness, priority 는 정수값을 갖고 recent_cpu, load_avg 는 실수값을 갖는다&lt;/b&gt;는 것이다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Niceness&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;NICE_MIN : -20, NICE_MAX : +20&lt;/li&gt;
&lt;li&gt;NICE_DEFAULT : 0&lt;/li&gt;
&lt;li&gt;이 값이 클수록 CPU time 을 다른 스레드에게 양보하는 정도가 크다. -&amp;gt; priority 가 낮아진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Priority&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;PRI_MIN : 0, PRI_MAX : +63&lt;/li&gt;
&lt;li&gt;PRI_DEFAULT : 31&lt;/li&gt;
&lt;li&gt;&lt;b&gt;priority = PRI_MAX - (recent_cpu / 4) - (nice * 2)&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;매&lt;/b&gt; &lt;b&gt;4 tick 마다 모든 스레드의 priority 를 재계산한다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;recent_cpu&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;실수 값을 가진다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;스레드가 최근에 얼마나 많은 CPU time 을 사용했는지를 나타내는 값이다.&lt;/li&gt;
&lt;li&gt;이 값이 클수록 최근에 더 많은 CPU 를 사용했음을 의미하므로 우선순위는 낮아진다.&lt;/li&gt;
&lt;li&gt;RECENT_CPU_DEFAULT : 0&lt;/li&gt;
&lt;li&gt;&lt;b&gt;recent_cpu = (2 * load_avg) / (2 * load_avg + 1) * recent_cpu + nice&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;매 tick 마다 running 스레드의 recent_cpu 값이 1 증가한다. (idle 스레드가 아닐 때)&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;매 1 초마다 모든 스레드의 recent_cpu 값을 재계산한다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;load_avg&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;실수 값을 가진다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;최근 1분 동안 수행 가능한 스레드의 평균 개수를 나타낸다.&lt;/li&gt;
&lt;li&gt;이 값이 크면 recent_cpu 값은 천천히 감소(priority 는 천천히 증가)하고, 작으면 recent_cpu 값이 빠르게 감소(priority 는 빠르게 증가)한다.&lt;/li&gt;
&lt;li&gt;이유는 수행 가능한 스레드의 평균 개수가 많을 때는 모든 스레드가 골고루 CPU time 을 배분받을 수 있도록 이미 사용한 스레드의&amp;nbsp; priority 가 천천히 증가하여야 하고, 평균 개수가 적을 때는 조금 더 빠르게 증가해도 모든 스레드가 골고루 CPU time 을 배분받을 수 있기 때문이다.&lt;/li&gt;
&lt;li&gt;LOAD_AVG_DEFAULT : 0&lt;/li&gt;
&lt;li&gt;&lt;b&gt;load_avg = (59/60) * load_avg + (1/60) * ready_threads&lt;/b&gt;, (ready_threads 는 ready + running 상태의 스레드의 개수)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;매 1 초마다 load_avg 값을 재계산한다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&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;h2 data-ke-size=&quot;size26&quot;&gt;Fixed-point arithmetic&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 알 수 있듯이 priority, nice 는 정수 값을, recent_cpu, load_avg 는 실수 값을 가진다. 여기서 문제가 하나 생기는데, 부동 소수점 연산은 복잡한 연산을 다루기 때문에 kernel 을 느리게 만들 가능성이 있다. 이에 따라 pintos kernel 은 부동소수점 연산을 지원하지 않으며 이로 인하여 kernel 에서 위에 나열한 식들의 직접적인 계산은 불가하다. 이를 해결하기 위해 fixed-point 연산을 다루어야 할 필요가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fixed-point 방식에 대한 자세한 설명 역시 4.4BSD Scheduler 포스트에 적어놓았다. 간단하게 얘기하자면, 총 32bit 중에서 1 bit(sign), 17 bit(정수부), 14 bit(소수부) 로 적용하여 실수를 int 형식으로 표현할 수 있도록 하는 것이다. 한 가지만 예를 들어보자. 만약 recent_cpu 가 2.75 라는 값을 가진다면 이 값은 fixed-point 로는 아래와 같이 나타낼 수 있다.&lt;/p&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 (sign bit +)&amp;nbsp; &amp;nbsp;&lt;span style=&quot;color: #ee2323;&quot;&gt;00000000000000010&lt;/span&gt; (정수부 2)&amp;nbsp; &lt;span style=&quot;color: #006dd7;&quot;&gt;11000000000000&lt;/span&gt; (소수부 0.75)] (소수부의 각 비트는 2^(-1), 2^(-2), ... )&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;= [0&lt;span style=&quot;color: #ee2323;&quot;&gt;00000000000000010&lt;/span&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;11000000000000&lt;/span&gt;]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;= 45056&lt;/p&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 recent_cpu = 45056 이라는 정수형으로 값이 저장된다. 이렇게 fp(fixed-point) 로 표현된 값들은 올바른 연산 값을 얻기 위해서 연산 시 특수한 보정 행위를 거쳐야 한다. pintos document 의 Appendix B.6 에 이에 대한 식이 기술되어 있다.&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;widthContent&quot; data-filename=&quot;스크린샷 2021-04-07 오전 11.39.54.png&quot; data-origin-width=&quot;958&quot; data-origin-height=&quot;672&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PjHlw/btq4H8USKEt/liawBV28XJQKGKAWYHaU10/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PjHlw/btq4H8USKEt/liawBV28XJQKGKAWYHaU10/img.png&quot; data-alt=&quot;fixed-point 의 연산 (pintos document)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PjHlw/btq4H8USKEt/liawBV28XJQKGKAWYHaU10/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPjHlw%2Fbtq4H8USKEt%2FliawBV28XJQKGKAWYHaU10%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;642&quot; height=&quot;672&quot; data-filename=&quot;스크린샷 2021-04-07 오전 11.39.54.png&quot; data-origin-width=&quot;958&quot; data-origin-height=&quot;672&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;fixed-point 의 연산 (pintos document)&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;우리가 가장 먼저 구현해야 하는 부분이다. Advanced Scheduler 의 모든 계산에서 fp 연산이 필요하므로 위의 연산식을 구현해둔다.&lt;/p&gt;
&lt;pre id=&quot;code_1620798424219&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* thread/fixed_point.h */
#define F (1 &amp;lt;&amp;lt; 14)
#define INT_MAX ((1 &amp;lt;&amp;lt; 31) - 1)
#define INT_MIN (-(1 &amp;lt;&amp;lt; 31))

int int_to_fp (int n) {
  return n * F;
}

int fp_to_int (int x) {
  return x / F;
}

int fp_to_int_round (int x) {
  if (x &amp;gt;= 0) return (x + F / 2) / F;
  else return (x - F / 2) / F;
}

int add_fp (int x, int y) {
  return x + y;
}

int sub_fp (int x, int y) {
  return x - y;
}

int add_mixed (int x, int n) {
  return x + n * F;
}

int sub_mixed (int x, int n) {
  return x - n * F;
}

int mult_fp (int x, int y) {
  return ((int64_t) x) * y / F;
}

int mult_mixed (int x, int n) {
  return x * n;
}

int div_fp (int x, int y) {
  return ((int64_t) x) * F / y;
}

int div_mixed (int x, int n) {
  return x / n;
}&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;각 연산식을 구현하기 위해 threads 폴더에 fixed_point.h 라는 헤더파일을 새로 만든 후 thread.c 에서 include 하여 사용하였다. 각 함수는 위의 연산식을 나타내는 그림의 식들을 같은 순서로 코드로 구현한 것이다. 몇 가지 자잘한 설명을 덧붙여보겠다. 모든 함수를 이해하였다면 넘어가도 좋다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;F 는 fixed point 1 을 나타낸다. (0 00000000000000001 00000000000000)&lt;/li&gt;
&lt;li&gt;INT_MAX 은 (0 11111111111111111 11111111111111), INT_MIN 은 (1 00000000000000000 00000000000000). (잘 모르겠다면 2의 보수(2's complement) 표현법을 찾아보라.)&lt;/li&gt;
&lt;li&gt;매개변수 x, y 는 fixed-point 형식의 실수이고, n 은 정수를 나타낸다. 모든 mixed 연산에서 n 은 두 번째 파라미터로 들어오게 한다.&lt;/li&gt;
&lt;li&gt;int fp_to_int_round () 에서 F / 2 는 fixed point 0.5 를 뜻한다. 0.5 를 더한 후 버림하면 반올림이 된다.&lt;/li&gt;
&lt;li&gt;int mult_fp () 에서 두 개의 fp 를 곱하면 32bit * 32bit 로 32bit 를 초과하는 값이 발생하기 때문에 x 를 64bit 로 일시적으로 바꿔준 후 곱하기 연산을 하여 오버플로우를 방지하고 계산 결과로 나온 64bit 값을 F 로 나눠서 다시 32bit 값으로 만들어 준다. (div_fp 도 마찬가지)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 data-ke-size=&quot;size26&quot;&gt;구현&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 새로운 자료구조를 추가한다. nice, recent_cpu 를 thread 구조체에 추가한다.&lt;/p&gt;
&lt;pre id=&quot;code_1620820204433&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* threads/thread.h */
#define PRI_MAX 63               
#define NICE_DEFAULT 0
#define RECENT_CPU_DEFAULT 0
#define LOAD_AVG_DEFAULT 0

struct thread
{
  ...
  int nice;
  int recent_cpu;
  ...
};&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 변수를 추가하면 초기화도 꼭 신경을 써주어야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1620820580866&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* threads/thread.c */
static void
init_thread (struct thread *t, const char *name, int priority)
{
  ...
  t-&amp;gt;nice = NICE_DEFAULT;
  t-&amp;gt;recent_cpu = RECENT_CPU_DEFAULT;
  ...
}&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;thread.c 에서는 fp 연산을 할 수 있도록 위에서 만든 fixed_point.h 파일을 include 하고 load_avg 를 global 변수로 선언하고 초기화까지 해준다.&lt;/p&gt;
&lt;pre id=&quot;code_1620820783817&quot; class=&quot;c++ arduino&quot; style=&quot;display: block; overflow: auto; padding: 15px; color: #383a42; background: #f6f7f8; font-size: 14px; border-radius: 3px; font-family: Menlo, Consolas, Monaco, monospace; border: 1px solid #dddddd; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* thread/thread.c */
#include &quot;threads/fixed_point.h&quot;

int load_avg;

void
thread_start (void)
{
  ...
  load_avg = LOAD_AVG_DEFAULT;
  ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 priority 를 계산하는 함수를 만들어주어야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1620820972822&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* threads/thread.c */
void
mlfqs_calculate_priority (struct thread *t)
{
  if (t == idle_thread) 
    return ;
  t-&amp;gt;priority = fp_to_int (add_mixed (div_mixed (t-&amp;gt;recent_cpu, -4), PRI_MAX - t-&amp;gt;nice * 2));
}&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;mlfqs_calculate_priority ()&lt;/b&gt; 함수는 특정 스레드의 priority 를 계산하는 함수이다. idle_thread 의 priority 는 고정이므로 제외하고, fixed_point.h 에서 만든 fp 연산 함수를 사용하여 priority 를 구한다. 계산 결과의 소수부분은 버림하고 정수의 priority 로 설정한다.&lt;/p&gt;
&lt;pre id=&quot;code_1620821458464&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* threads/thread.c */
void
mlfqs_calculate_recent_cpu (struct thread *t)
{
  if (t == idle_thread)
    return ;
  t-&amp;gt;recent_cpu = add_mixed (mult_fp (div_fp (mult_mixed (load_avg, 2), add_mixed (mult_mixed (load_avg, 2), 1)), t-&amp;gt;recent_cpu), t-&amp;gt;nice);
}&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;mlfqs_calculate_recent_cpu ()&lt;/b&gt; 함수는 스레드의 recent_cpu 값을 계산하는 함수이다. fp 연산으로 인해 다소 복잡해보일 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1620821660256&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* threads/thread.c */
void 
mlfqs_calculate_load_avg (void) 
{
  int ready_threads;
  
  if (thread_current () == idle_thread)
    ready_threads = list_size (&amp;amp;ready_list);
  else
    ready_threads = list_size (&amp;amp;ready_list) + 1;

  load_avg = add_fp (mult_fp (div_fp (int_to_fp (59), int_to_fp (60)), load_avg), 
                     mult_mixed (div_fp (int_to_fp (1), int_to_fp (60)), ready_threads));
}&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;mlfqs_calculate_load_avg ()&lt;/b&gt; 함수는 load_avg 값을 계산하는 함수이다. load_avg 값을 스레드 고유의 값이 아니라 system wide 값이기 때문에 idle_thread 가 실행되는 경우에도 계산하여 준다. ready_threads 는 현재 시점에서 실행 가능한 스레드의 수를 나타내므로 ready_list 에 들어있는 스레드의 숫자에 현재 running 스레드 1개를 더한다. (idle_thread 는 실행 가능한 스레드에 포함시키지 않음).&lt;/p&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;이제 각 값들이 변하는 시점에 수행할 함수들을 만들어보자. 이 값들이 변하는 시점은 3 경우가 있었다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;1 tick 마다 running 스레드의 recent_cpu 값 + 1&lt;/li&gt;
&lt;li&gt;4 tick 마다 모든 스레드의 priority 재계산&lt;/li&gt;
&lt;li&gt;1 초마다 모든 스레드의 recent_cpu 값과 load_avg 값 재계산&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1620822115701&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* threads/thread.c */
void
mlfqs_increment_recent_cpu (void)
{
  if (thread_current () != idle_thread)
    thread_current ()-&amp;gt;recent_cpu = add_mixed (thread_current ()-&amp;gt;recent_cpu, 1);
}

void
mlfqs_recalculate_recent_cpu (void)
{
  struct list_elem *e;

  for (e = list_begin (&amp;amp;all_list); e != list_end (&amp;amp;all_list); e = list_next (e)) {
    struct thread *t = list_entry (e, struct thread, allelem);
    mlfqs_calculate_recent_cpu (t);
  }
}

void
mlfqs_recalculate_priority (void)
{
  struct list_elem *e;

  for (e = list_begin (&amp;amp;all_list); e != list_end (&amp;amp;all_list); e = list_next (e)) {
    struct thread *t = list_entry (e, struct thread, allelem);
    mlfqs_calculate_priority (t);
  }
}&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;맨 위부터 차례대로 현재 스레드의 recent_cpu 의 값을 1 증가시키는 함수, 모든 스레드의 recent_cpu 를 재계산 하는 함수, 모든 스레드의 priority 를 재계산 하는 함수이다. 이제 각 함수를 해당하는 시간 주기마다 실행되도록 timer_interrupt 함수를 바꾸어주면 된다. &lt;b&gt;&amp;lt;devices/timer.c&amp;gt;&lt;/b&gt; 에 있는 timer_interrupt 함수에서 일정 시간마다 tick 을 1 씩 증가시켜 주고 있다. 여기에 조건을 달아주자.&lt;/p&gt;
&lt;pre id=&quot;code_1620822369576&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* devices/timer.c */
static void
timer_interrupt (struct intr_frame *args UNUSED)
{
  ticks++;
  thread_tick ();

  if (thread_mlfqs) {
    mlfqs_increment_recent_cpu ();
    if (ticks % 4 == 0) {
      mlfqs_recalculate_priority ();
      if (ticks % TIMER_FREQ == 0) {
        mlfqs_recalculate_recent_cpu ();
        mlfqs_calculate_load_avg ();
      }
    }
  }

  thread_awake (ticks);
}&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;mlfqs 옵션이 들어왔을 때에만 advanced scheduler 가 작동할 수 있도록 if 문으로 묶고 각 시간에 맞게 priority, recent_cpu, load_avg 값의 조정이 실행되게 하였다. TIMER_FREQ 값은 1초에 몇 개의 ticks 이 실행되는지를 나타내는 값으로 thread.h 에 100 으로 정의되어 있다. 이에 따라 pintos kernel 은 1 초에 100 ticks 가 실행되고 1 ticks = 1 ms 를 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 가지 더 신경써주어야 하는 것은 lock_acquire (), lock_release () 함수에서 구현해주었던 &lt;b&gt;priority donation 을 mlfqs 에서는 비활성화&lt;/b&gt;시켜주어야 한다. mlfqs 스케줄러는 시간에 따라 priority 가 재조정되므로 priority donation 을 사용하지 않는다.&lt;/p&gt;
&lt;pre id=&quot;code_1620822719094&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* threads/synch.c */
void
lock_acquire (struct lock *lock)
{
  if (thread_mlfqs) {
    sema_down (&amp;amp;lock-&amp;gt;semaphore);
    lock-&amp;gt;holder = thread_current ();
    return ;
  }
  ...
}

void
lock_release (struct lock *lock) 
{
  lock-&amp;gt;holder = NULL;
  if (thread_mlfqs) {
    sema_up (&amp;amp;lock-&amp;gt;semaphore);
    return ;
  }
  ...
}&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 mlfqs 스케줄러에서는 priority 를 임의로 변경할 수 없기 때문에 thread_set_priority () 함수 역시 비활성화 시켜야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1620823133237&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* threads/thread.c */
void
thread_set_priority (int new_priority) 
{
  if (thread_mlfqs)
    return;
  ...
}&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;마지막으로 pintos 의 선언만 되어 있고 비어있는 아래 함수들을 채워주자.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1620822937421&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* threads/thread.c */
void thread_set_nice (int);
int thread_get_nice (void);
int thread_get_load_avg (void);
int thread_get_recent_cpu (void);&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;/p&gt;
&lt;pre id=&quot;code_1620823260102&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* threads/thread.c */
void
thread_set_nice (int nice UNUSED) 
{ // 현재 스레드의 nice 값을 새 값으로 설정
  enum intr_level old_level = intr_disable ();
  thread_current ()-&amp;gt;nice = nice;
  mlfqs_calculate_priority (thread_current ());
  thread_test_preemption ();
  intr_set_level (old_level);
}

int
thread_get_nice (void) 
{ // 현재 스레드의 nice 값을 반환
  enum intr_level old_level = intr_disable ();
  int nice = thread_current ()-&amp;gt; nice;
  intr_set_level (old_level);
  return nice;
}

int
thread_get_load_avg (void) 
{ // 현재 시스템의 load_avg * 100 값을 반환
  enum intr_level old_level = intr_disable ();
  int load_avg_value = fp_to_int_round (mult_mixed (load_avg, 100));
  intr_set_level (old_level);
  return load_avg_value;
}

int
thread_get_recent_cpu (void) 
{ // 현재 스레드의 recent_cpu * 100 값을 반환
  enum intr_level old_level = intr_disable ();
  int recent_cpu= fp_to_int_round (mult_mixed (thread_current ()-&amp;gt;recent_cpu, 100));
  intr_set_level (old_level);
  return recent_cpu;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;recent_cpu, load_avg 의 getter 는 pintos document 의 지시대로 100 을 곱한 후 정수형으로 만들고 반올림하여 반환하도록 하였다. 정수형 반환값에서 소수점 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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결과&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자 이제 구현이 마무리 되었으니 make check 를 돌려보도록 하자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;438&quot; data-origin-height=&quot;526&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c9aPBT/btq4Otcm6kx/kel3Iu9a7n6msHbmOhr5l0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c9aPBT/btq4Otcm6kx/kel3Iu9a7n6msHbmOhr5l0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c9aPBT/btq4Otcm6kx/kel3Iu9a7n6msHbmOhr5l0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc9aPBT%2Fbtq4Otcm6kx%2Fkel3Iu9a7n6msHbmOhr5l0%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;438&quot; height=&quot;526&quot; data-origin-width=&quot;438&quot; data-origin-height=&quot;526&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;드디어 Project 1 - Thread 의 모든 테스트를 통과하였다!! pintos 프로젝트를 진행하면서 구현 과정 보다도 구현을 위한 background 공부에서 너무 많은 것을 배우고 느끼고 있다. 컴퓨터의 동작을 하나하나 밑바닥부터 조각내어 뜯어보는 느낌이라 이해하기 어려웠던 부분도 있지만, 모든 조각이 감탄스러웠고 이 조각들이 합쳐져서 kernel 로 동작하는 것을 보니 컴퓨터와 프로그램이 얼마나 위대한 발명품인지 온몸으로 느낄 수 있었다. 앞으로 남은 projects 들을 진행하는 것도 잔뜩 기대중이니 다음 포스트도 기다려주세요!&lt;/p&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;</description>
      <category>프로젝트/Pintos</category>
      <category>4.4BSD scheduler</category>
      <category>advanced scheduler</category>
      <category>mlfqs</category>
      <category>Operating System</category>
      <category>pintos</category>
      <category>운영체제</category>
      <author>aa_rong_blog</author>
      <guid isPermaLink="true">https://poalim.tistory.com/36</guid>
      <comments>https://poalim.tistory.com/36#entry36comment</comments>
      <pubDate>Wed, 12 May 2021 15:06:29 +0900</pubDate>
    </item>
    <item>
      <title>[Pintos] Project 1 : Thread(스레드) - Priority Inversion(donation)</title>
      <link>https://poalim.tistory.com/35</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;드디어 Priority Scheduling 의 마지막 파트인 Priority Inversion 까지 왔다. 이전에 다루었던 문제들보다 꽤 많이 복잡해서 생각보다 시간이 오래 걸렸다ㅜㅜ 시작해보자!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Priority Inversion&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핀토스 Documents 에 나와있는 설명을 해석해보자. 우선 &lt;b&gt;priority inversion&lt;/b&gt; 이란 다음과 같은 상황에서 발생한다. H (high), M (medium), L (low) 라는 세 개의 스레드가 있고 각각의 우선순위는 H &amp;gt; M &amp;gt; L 일 때, H 가 L 을 기다려야 하는 상황(예를 들어, H 가 lock 을 요청했는데 이 lock 을 L 이 점유하고 있는 경우)이 생긴다면 H 가 L 에게 CPU 점유를 넘겨주면 M 이 L 보다 우선순위가 높으므로 점유권을 선점하여 실행되어서 스레드가 마무리 되는 순서가 M-&amp;gt;L-&amp;gt;H 가 되어서 M 이 더 높은 우선순위를 가진 H 보다 우선하여 실행되는 문제가 발생한다. 이를 잘 나타낸 그림이 있어서 가져와 보았다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-filename=&quot;스크린샷 2021-05-01 오후 10.22.33.png&quot; data-origin-width=&quot;574&quot; data-origin-height=&quot;301&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cL6NRC/btq3S0jNubE/X5KSaVQCwrVAu0Pg2jT2Vk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cL6NRC/btq3S0jNubE/X5KSaVQCwrVAu0Pg2jT2Vk/img.png&quot; data-alt=&quot;출처 : https://oslab.kaist.ac.kr&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cL6NRC/btq3S0jNubE/X5KSaVQCwrVAu0Pg2jT2Vk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcL6NRC%2Fbtq3S0jNubE%2FX5KSaVQCwrVAu0Pg2jT2Vk%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;574&quot; height=&quot;301&quot; data-filename=&quot;스크린샷 2021-05-01 오후 10.22.33.png&quot; data-origin-width=&quot;574&quot; data-origin-height=&quot;301&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 : https://oslab.kaist.ac.kr&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 문제를 해결하기 위한 방법으로 &lt;b&gt;priority donation&lt;/b&gt; 을 구현하여야 한다. priority donation 이란 위의 상황에서 H 가 자신이 가진 priority 를 L 에게 일시적으로 양도하여서 H 자신의 실행을 위해 L 이 자신과 동일한 우선순위를 가져서 우선적으로 실행될 수 있도록 만드는 방법이다. priority donation 이 작동하면 실행 순서는 아래와 같이 바뀐다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-filename=&quot;스크린샷 2021-05-01 오후 10.26.59.png&quot; data-origin-width=&quot;575&quot; data-origin-height=&quot;300&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/prXzR/btq3TxIhUhM/gJH7wiqPbktxjYFdg9E3jK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/prXzR/btq3TxIhUhM/gJH7wiqPbktxjYFdg9E3jK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/prXzR/btq3TxIhUhM/gJH7wiqPbktxjYFdg9E3jK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FprXzR%2Fbtq3TxIhUhM%2FgJH7wiqPbktxjYFdg9E3jK%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;575&quot; height=&quot;300&quot; data-filename=&quot;스크린샷 2021-05-01 오후 10.26.59.png&quot; data-origin-width=&quot;575&quot; data-origin-height=&quot;300&quot;/&gt;&lt;/span&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;핀토스 documents 에서는 priority donation 이 일어난 수 있는 모든 상황을 고려해야 한다고 하면서 &lt;b&gt;Multiple donation, Nested donation&lt;/b&gt; 두 가지를 언급한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Multiple donation&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단어에서 알 수 있듯이 여러번의 donation 이 일어난 상황이라고 볼 수 있다. 위의 예시에서 L 가 lock A, lock B 라는 두 가지 lock 을 점유하고 있다고 해보자. H 가 lock A 를, M 이 lock B 를 각각 요청하였을 때, L 은 자신이 가지고 있는 lock 을 요청한 모든 스레드에게 priority 를 양도받고, 이 중 가장 높은 priority 를 일시적으로 자신의 priority 로 갖는다. 그림으로 보면 아래와 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-filename=&quot;스크린샷 2021-05-01 오후 10.43.20.png&quot; data-origin-width=&quot;523&quot; data-origin-height=&quot;346&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cGL3hf/btq3XcijstP/XRIKLkfWjfdlhyZE6Rv0m0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cGL3hf/btq3XcijstP/XRIKLkfWjfdlhyZE6Rv0m0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cGL3hf/btq3XcijstP/XRIKLkfWjfdlhyZE6Rv0m0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcGL3hf%2Fbtq3XcijstP%2FXRIKLkfWjfdlhyZE6Rv0m0%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;472&quot; height=&quot;346&quot; data-filename=&quot;스크린샷 2021-05-01 오후 10.43.20.png&quot; data-origin-width=&quot;523&quot; data-origin-height=&quot;346&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우에 L 이 lock B 를 release 해도 아직 M 에게 양도받은 priority 가 있기 때문에 31 이 아닌 32의 priority 를 가지게 된다.&lt;/p&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;​&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Nested donation&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-filename=&quot;스크린샷 2021-05-01 오후 10.48.03.png&quot; data-origin-width=&quot;742&quot; data-origin-height=&quot;177&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oGzWv/btq3TrnUWZ7/M76pQFJIstnIDPMpDIUkSK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oGzWv/btq3TrnUWZ7/M76pQFJIstnIDPMpDIUkSK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oGzWv/btq3TrnUWZ7/M76pQFJIstnIDPMpDIUkSK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoGzWv%2Fbtq3TrnUWZ7%2FM76pQFJIstnIDPMpDIUkSK%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;742&quot; height=&quot;177&quot; data-filename=&quot;스크린샷 2021-05-01 오후 10.48.03.png&quot; data-origin-width=&quot;742&quot; data-origin-height=&quot;177&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그림에서 볼 수 있듯이 H 가 lock B 를 얻기 위해 M 에게 priority donation 을 행하고 M 은 lock A 를 얻기 위해 L 에게 priority donation 을 행하는 것 처럼, 여러번의 단계적 donation 이 일어나는 상황이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-filename=&quot;스크린샷 2021-05-01 오후 10.50.50.png&quot; data-origin-width=&quot;753&quot; data-origin-height=&quot;386&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/q6k87/btq3TVB4KFz/OIUe5u6WIWxPL23GGYqUWk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/q6k87/btq3TVB4KFz/OIUe5u6WIWxPL23GGYqUWk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/q6k87/btq3TVB4KFz/OIUe5u6WIWxPL23GGYqUWk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fq6k87%2Fbtq3TVB4KFz%2FOIUe5u6WIWxPL23GGYqUWk%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;632&quot; height=&quot;386&quot; data-filename=&quot;스크린샷 2021-05-01 오후 10.50.50.png&quot; data-origin-width=&quot;753&quot; data-origin-height=&quot;386&quot;/&gt;&lt;/span&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;struct thread&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 각 스레드가 양도받은 내역을 관리할 수 있게 thread 의 구조체를 변경해줄 필요가 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1619878514457&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct thread
  {
    ...
    int init_priority;
    
    struct lock *wait_on_lock;
    struct list donations;
    struct list_elem donation_elem;
	...
  };&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4개 정도의 항목을 추가해주었다.&lt;b&gt; init_priority&lt;/b&gt; 는 스레드가 priority 를 양도받았다가 다시 반납할 때 원래의 priority 를 복원할 수 있도록 고유의 priority 값을 저장하는 변수이다. &lt;b&gt;wait_on_lock&lt;/b&gt; 은 스레드가 현재 얻기 위해 기다리고 있는 lock 으로 스레드는 이 lock 이 release 되기를 기다린다. &lt;b&gt;donations&lt;/b&gt; 는 자신에게 priority 를 나누어준 스레드들의 리스트이고 &lt;b&gt;donation_elem&lt;/b&gt; 은 이 리스트를 관리하기 위한 element 로 thread 구조체의 그냥 elem 과 구분하여 사용하도록 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-filename=&quot;스크린샷 2021-05-01 오후 11.21.35.png&quot; data-origin-width=&quot;451&quot; data-origin-height=&quot;282&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhdHes/btq3TKHy8gI/lhXPULhKSkFYh5XlaorVa0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhdHes/btq3TKHy8gI/lhXPULhKSkFYh5XlaorVa0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhdHes/btq3TKHy8gI/lhXPULhKSkFYh5XlaorVa0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbhdHes%2Fbtq3TKHy8gI%2FlhXPULhKSkFYh5XlaorVa0%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;451&quot; height=&quot;282&quot; data-filename=&quot;스크린샷 2021-05-01 오후 11.21.35.png&quot; data-origin-width=&quot;451&quot; data-origin-height=&quot;282&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;multiple donation 에서 사용했던 그림으로 예시를 들면, &lt;b&gt;L-&amp;gt;donations = [M, H],&amp;nbsp; &amp;nbsp;M-&amp;gt;wait_on_lock = A,&amp;nbsp; &amp;nbsp;H-&amp;gt;wait_on_lock = B&lt;/b&gt; 이고, &lt;b&gt;L-&amp;gt;init_priority = 31&lt;/b&gt; 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 요소를 추가하였으면 언제나 초기화는 기본이다. init_thread 에서 초기화 코드를 추가해준다.&lt;/p&gt;
&lt;pre id=&quot;code_1619879190659&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* thread/thread.c */
static void
init_thread (struct thread *t, const char *name, int priority)
{
  ...
  t-&amp;gt;init_priority = priority;
  t-&amp;gt;wait_on_lock = NULL;
  list_init (&amp;amp;t-&amp;gt;donations);
  ...
}&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;lock_acquire (struct lock *lock)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;lock_acquire() 함수는 스레드가 lock 을 요청할 때 실행된다. lock 을 현재 점유하고 있는 스레드가 없으면 상관 없지만, 누군가 점유하고 있다면 자신의 priority 를 양도하여 lock 을 점유하고 있는 스레드가 우선적으로 lock 을 반환하도록 해야한다. 우선 현재 lock_acquire() 가 어떻게 구현되어 있는지 보자.&lt;/p&gt;
&lt;pre id=&quot;code_1619878104265&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* thread/synch.c */
void
lock_acquire (struct lock *lock)
{
  ASSERT (lock != NULL);
  ASSERT (!intr_context ());
  ASSERT (!lock_held_by_current_thread (lock));

  sema_down (&amp;amp;lock-&amp;gt;semaphore);
  lock-&amp;gt;holder = thread_current ();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;lock 에대한 요청이 들어오면 sema_down 에서 멈췄다가 lock 이 사용가능하게 되면 자신이 lock 을 선점하는 것을 볼 수 있다. sema_down 을 기점으로 이전은 lock 을 얻기 전, 이후는 lock 을 얻은 후이다. sema_down 에 들어가기 전에 lock 을 가지고 있는 스레드에게 priority 를 양도하는 작업이 필요하다.&lt;/p&gt;
&lt;pre id=&quot;code_1619879357876&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* thread/synch.c */
void
lock_acquire (struct lock *lock)
{
  ASSERT (lock != NULL);
  ASSERT (!intr_context ());
  ASSERT (!lock_held_by_current_thread (lock));

  struct thread *cur = thread_current ();
  if (lock-&amp;gt;holder) {
    cur-&amp;gt;wait_on_lock = lock;
    list_insert_ordered (&amp;amp;lock-&amp;gt;holder-&amp;gt;donations, &amp;amp;cur-&amp;gt;donation_elem, 
    			thread_compare_donate_priority, 0);
    donate_priority ();
  }

  sema_down (&amp;amp;lock-&amp;gt;semaphore);
  
  cur-&amp;gt;wait_on_lock = NULL;
  lock-&amp;gt;holder = cur;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;lock-&amp;gt;holder 는 현재 lock 을 소유하고 있는 스레드를 가르킨다. 현재 lock 을 소유하고 있는 스레드가 없다면 바로 해당 lock 을 차지하고 lock 을 누군가 소유하고 있다면 그 스레드에게 priority 를 넘겨주어야 한다. 여기서 한 가지 알아야 할 점은, 현재 lock_acquire() 를 요청하는 스레드가 실행되고 있다는 자체로 lock 을 가지고 있는 스레드보다 우선순위가 높다는 뜻이기 때문에 if (cur-&amp;gt;priority &amp;gt; lock-&amp;gt;holder-&amp;gt;priority) 등의 비교 조건은 필요하지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;lock 을 점유하고 있는 스레드(holder) 가 있다면 현재 스레드의 wait_on_lock 에 lock 을 추가하고 holder 의 donations 리스트에 현재 스레드를 추가하고 priority donation 을 실행한다. thread_compare_donate_priority 함수는 thread_compare_priority 의 역할을 donation_elem 에 대하여 하는 함수이다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1619879921318&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* thread/thread.c */
bool
thread_compare_donate_priority (const struct list_elem *l, 
				const struct list_elem *s, void *aux UNUSED)
{
	return list_entry (l, struct thread, donation_elem)-&amp;gt;priority
		 &amp;gt; list_entry (s, struct thread, donation_elem)-&amp;gt;priority;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;donate_priority () 함수는 뒤에서 살펴보기로 하고, sema_down 이후에는 현재 스레드가 lock 을 차지한 상태이므로 wait_on_lock 을 NULL 로 만들어주고 lock-&amp;gt;holder 를 현재 스레드로 만들어준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;donate_priority ()&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 함수는 자신의 priority 를 필요한 lock 을 점유하고 있는 스레드에게 빌려주는 함수이다. 한 가지 주의할 점은, nested donation 에서 살펴본 것처럼 하위에 연결된 모든 스레드에 donation 이 일어난다는 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-filename=&quot;스크린샷 2021-05-01 오후 11.45.01.png&quot; data-origin-width=&quot;754&quot; data-origin-height=&quot;184&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eggh6i/btq3UTw0VlI/tSwJ1MFvPeboHpBvzQZbhK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eggh6i/btq3UTw0VlI/tSwJ1MFvPeboHpBvzQZbhK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eggh6i/btq3UTw0VlI/tSwJ1MFvPeboHpBvzQZbhK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Feggh6i%2Fbtq3UTw0VlI%2FtSwJ1MFvPeboHpBvzQZbhK%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;570&quot; height=&quot;184&quot; data-filename=&quot;스크린샷 2021-05-01 오후 11.45.01.png&quot; data-origin-width=&quot;754&quot; data-origin-height=&quot;184&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 살펴본 위와 같은 상황에서 H 는 M 에게 전달하고 M 은 이 priority 를 다시 L 에게 전달하여서 L 이 우선적으로 실행될 수 있도록 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1619880484651&quot; class=&quot;c++ arduino&quot; style=&quot;display: block; overflow: auto; padding: 15px; color: #383a42; background: #f6f7f8; font-size: 14px; border-radius: 3px; font-family: Menlo, Consolas, Monaco, monospace; border: 1px solid #dddddd; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* thread/thread.c */
void
donate_priority (void)
{
  int depth;
  struct thread *cur = thread_current ();

  for (depth = 0; depth &amp;lt; 8; depth++){
    if (!cur-&amp;gt;wait_on_lock) break;
      struct thread *holder = cur-&amp;gt;wait_on_lock-&amp;gt;holder;
      holder-&amp;gt;priority = cur-&amp;gt;priority;
      cur = holder;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구현은 간단하게 가능하다. depth 는 nested 의 최대 깊이를 지정해주기 위해 사용했다(max_depth = 8). 스레드의 wait_on_lock 이 NULL 이 아니라면 스레드가 lock 에 걸려있다는 소리이므로 그 lock 을 점유하고 있는 holder 스레드에게 priority 를 넘겨주는 방식을 깊이 8의 스레드까지 반복한다. wait_on_lock == NULL 이라면 더 이상 donation 을 진해할 필요가 없으므로 break 해준다. 여기까지 구현이 됐다면 priority 의 양도는 성공적으로 수행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;lock_release (struct lock *lock)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 스레드가 priority 를 양도받아 critical section 을 마치고 lock 을 반환할 때의 경우를 만들어주어야 한다. 우선 현재 lock_release() 함수가 어떻게 생겼는지 확인해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1619881042839&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* thread/synch.c */
void
lock_release (struct lock *lock) 
{
  ASSERT (lock != NULL);
  ASSERT (lock_held_by_current_thread (lock));

  lock-&amp;gt;holder = NULL;
  sema_up (&amp;amp;lock-&amp;gt;semaphore);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재는 그냥 lock 이 가진 holder 를 비워주고 sema_up 하여주는 게 전부이다. sema_up 하여 lock 의 점유를 반환하기 전에 현재 이 lock 을 사용하기 위해 나에게 priority 를 빌려준 스레드들을 donations 리스트에서 제거하고, priority 를 재설정 해주는 작업이 필요하다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-filename=&quot;스크린샷 2021-05-01 오후 11.21.35.png&quot; data-origin-width=&quot;451&quot; data-origin-height=&quot;282&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhdHes/btq3TKHy8gI/lhXPULhKSkFYh5XlaorVa0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhdHes/btq3TKHy8gI/lhXPULhKSkFYh5XlaorVa0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhdHes/btq3TKHy8gI/lhXPULhKSkFYh5XlaorVa0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbhdHes%2Fbtq3TKHy8gI%2FlhXPULhKSkFYh5XlaorVa0%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;451&quot; height=&quot;282&quot; data-filename=&quot;스크린샷 2021-05-01 오후 11.21.35.png&quot; data-origin-width=&quot;451&quot; data-origin-height=&quot;282&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 한 번 위 그림을 예로 들어보면, thread L 에서 lock_release(lock B) 를 실행하면 lock B 를 사용하기 위해서 priority 를 나누어 주었던 Thread H 는 이제 priority 를 빌려줘야 할 이유가 없으므로 donations 리스트에서 Thread H 를 지워주어야 한다. 그 후 Thread H 가 빌려주던 33 이라는 priority 를 지우고 Thread L 의 priority 를 다시 정해주어야 하는데, 이 때 남아있는 donations 리스트에서 가장 높은 priority 를 가지고 있는 스레드의 priority 를 받아서 Thread L 의 priority 로 설정해준다. 만약 더 이상 donations 리스트에 남아있는 스레드가 없다면 원래 값인 init_priority 로 priority 를 설정해주면 된다. 우선 donations 리스트에서 스레드들을 지우는 함수를 만들어보자.&lt;/p&gt;
&lt;pre id=&quot;code_1619881735347&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* thread/thread.c */
void
remove_with_lock (struct lock *lock)
{
  struct list_elem *e;
  struct thread *cur = thread_current ();

  for (e = list_begin (&amp;amp;cur-&amp;gt;donations); e != list_end (&amp;amp;cur-&amp;gt;donations); e = list_next (e)){
    struct thread *t = list_entry (e, struct thread, donation_elem);
    if (t-&amp;gt;wait_on_lock == lock)
      list_remove (&amp;amp;t-&amp;gt;donation_elem);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cur-&amp;gt;donations 리스트를 처음부터 끝까지 돌면서 리스트에 있는 스레드가 priority 를 빌려준 이유, 즉 wait_on_lock 이 이번에 release 하는 lock 이라면 해당하는 스레드를 리스트에서 지워준다. 이 때 &lt;b&gt;&amp;lt;list/kernel/list.c&amp;gt;&lt;/b&gt; 에 구현되어 있는 &lt;b&gt;list_remove&lt;/b&gt; 함수를 사용하였다. 모든 donations 리스트와 관련된 작업에서는 elem 이 아니라 donation_elem 을 사용하여야 함을 명심하자. 다음은 priority 를 재설정하는 함수를 보자.&lt;/p&gt;
&lt;pre id=&quot;code_1619882141641&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* thread/thread.c */
void
refresh_priority (void)
{
  struct thread *cur = thread_current ();

  cur-&amp;gt;priority = cur-&amp;gt;init_priority;
  
  if (!list_empty (&amp;amp;cur-&amp;gt;donations)) {
    list_sort (&amp;amp;cur-&amp;gt;donations, thread_compare_donate_priority, 0);

    struct thread *front = list_entry (list_front (&amp;amp;cur-&amp;gt;donations), struct thread, donation_elem);
    if (front-&amp;gt;priority &amp;gt; cur-&amp;gt;priority)
      cur-&amp;gt;priority = front-&amp;gt;priority;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;donations 리스트가 비어있다면 init_priority 로 설정되고 donations 리스트에 스레드가 남아있다면 남아있는 스레드 중에서 가장 높은 priority 를 가져와야 한다. priority 가 가장 높은 스레드를 고르기 위해 list_sort 를 사용하여 donations 리스트의 원소들을 priority 순으로 내림차순 정렬한다. 그 후 맨 앞의 스레드(priority 가 가장 큰 스레드)를 뽑아서 init_priority 와 비교하여 둘 중 더 큰 priority 를 적용한다. 이제 새로 만든 함수들로 lock_release() 함수를 완성하여 보자.&lt;/p&gt;
&lt;pre id=&quot;code_1619882383972&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* thread/synch.c */
void
lock_release (struct lock *lock) 
{
  ASSERT (lock != NULL);
  ASSERT (lock_held_by_current_thread (lock));

  remove_with_lock (lock);
  refresh_priority ();
  
  lock-&amp;gt;holder = NULL;
  sema_up (&amp;amp;lock-&amp;gt;semaphore);
}&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;한 가지 생각하여야 하는 상황이 더 있다. 만약 현재 진행중인 running thread 의 priority 변경이 일어났을 때 (이 경우 init_priority 의 변경을 의미한다.) donations 리스트들에 있는 스레드들보다 priority 가 높아지는 경우가 생길 수 있다. 이 경우 priority 는 donations 리스트 중 가장 높은 priority 가 아니라 새로 바뀐 priority 가 적용될 수 있게 해야 한다. 이는 thread_set_priority 에 refresh_priority() 함수를 추가하는 것으로 간단하게 가능하다.&lt;/p&gt;
&lt;pre id=&quot;code_1619882688781&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* thread/thread.c */
void
thread_set_priority (int new_priority) 
{
  thread_current ()-&amp;gt;init_priority = new_priority;
  
  refresh_priority ();
  thread_test_preemption ();
}&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;widthContent&quot; data-filename=&quot;스크린샷 2021-05-02 오전 12.25.49.png&quot; data-origin-width=&quot;472&quot; data-origin-height=&quot;502&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/coKNqy/btq3XcbACAR/WblVSxY9rBxAogpYn1esXK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/coKNqy/btq3XcbACAR/WblVSxY9rBxAogpYn1esXK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/coKNqy/btq3XcbACAR/WblVSxY9rBxAogpYn1esXK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcoKNqy%2Fbtq3XcbACAR%2FWblVSxY9rBxAogpYn1esXK%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;472&quot; height=&quot;502&quot; data-filename=&quot;스크린샷 2021-05-02 오전 12.25.49.png&quot; data-origin-width=&quot;472&quot; data-origin-height=&quot;502&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저번에 fail 을 받았던 priority 관련 테스트들을 순식간에 모두 통과하였다! 생각보다 많이 복잡하고 시간도 오래 걸렸지만 그만큼 뿌듯한 마음도 크다. 다음 시간에는 Advanced Scheduler 를 구현해본다. 나머지 7개의 테스트에 관한 내용으로 background 포스트에서 nice, load_avg 값의 계산을 보았다면 알겠지만 상당히 어려워보인다. 화이팅!&lt;/p&gt;</description>
      <category>프로젝트/Pintos</category>
      <category>os</category>
      <category>pintos</category>
      <category>priority donation</category>
      <category>priority inversion</category>
      <category>priority scheduling</category>
      <category>scheduler</category>
      <category>운영체제</category>
      <author>aa_rong_blog</author>
      <guid isPermaLink="true">https://poalim.tistory.com/35</guid>
      <comments>https://poalim.tistory.com/35#entry35comment</comments>
      <pubDate>Sun, 2 May 2021 00:29:23 +0900</pubDate>
    </item>
    <item>
      <title>[Pintos] Project 1 : Thread(스레드) - Priority Scheduling(2)</title>
      <link>https://poalim.tistory.com/34</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 동기화 도구들의 스케쥴링 방식을 살펴보자. 스케쥴링 도구들의 코드는 &lt;b&gt;thread 폴더의&lt;/b&gt; &lt;b&gt;synch.h, synch.c&lt;/b&gt; 에 구현되어 있다. 우리가 다루어야 할 동기화 도구는 lock, semaphore, condition variable 3가지이다. 하나씩 살펴보자.&lt;/p&gt;
&lt;pre id=&quot;code_1619738858360&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* thread/synch.h */
struct semaphore 
  {
    unsigned value;             /* Current value. */
    struct list waiters;        /* List of waiting threads. */
  };
  
struct lock 
  {
    struct thread *holder;      /* Thread holding lock (for debugging). */
    struct semaphore semaphore; /* Binary semaphore controlling access. */
  };
  
struct condition 
  {
    struct list waiters;        /* List of waiting threads. */
  };&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;semaphore&lt;/b&gt; 은 공유 자원의 개수를 나타내는 value 와 공유 자원을 사용하기 위해 대기하는 waiters 의 리스트를 가진다. &lt;b&gt;lock&lt;/b&gt; 은 value = 1인 특별한 semaphore 로 구현되어 있고, 현재 lock 을 가지고 있는 thread 정보와 semaphore 를 멤버로 가진다. &lt;b&gt;condition&lt;/b&gt; 은 condition variables 가 만족하기를 기다리는 waiters 의 리스트를 가진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Semaphore&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;semaphore 를 예로 들면, 하나의 공유자원을 사용하기 위해 여러 스레드가 sema_down 되어 대기하고 있다고 할 때, 이 공유자원을 사용하던 스레드가 공유자원의 사용을 마치고 sema_up 할 때 어떤 스레드가 가장 먼저 이 공유자원을 차지할 것인가에 대한 문제가 스케쥴링에서 해결해야 하는 것이다. 그럼 현재 어떻게 이루어져 있는지 코드를 살펴보자.&lt;/p&gt;
&lt;pre id=&quot;code_1619739430197&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* thread/synch.c */
void
sema_down (struct semaphore *sema) 
{
  enum intr_level old_level;

  ASSERT (sema != NULL);
  ASSERT (!intr_context ());

  old_level = intr_disable ();
  while (sema-&amp;gt;value == 0) 
    {
      list_push_back (&amp;amp;sema-&amp;gt;waiters, &amp;amp;thread_current ()-&amp;gt;elem);
      thread_block ();
    }
  sema-&amp;gt;value--;
  intr_set_level (old_level);
}

void
sema_up (struct semaphore *sema) 
{
  enum intr_level old_level;

  ASSERT (sema != NULL);

  old_level = intr_disable ();
  if (!list_empty (&amp;amp;sema-&amp;gt;waiters))
  {
    thread_unblock (list_entry (list_pop_front (&amp;amp;sema-&amp;gt;waiters),
                                struct thread, elem));
  }
  sema-&amp;gt;value++;
  intr_set_level (old_level);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;semaphore 를 다루는 함수는 sema_down, sema_up 두 가지이다. 공유 자원을 사용하고자 하는 스레드는 sema_down 을 실행하고, 사용 가능한 공유자원이 없다면 sema-&amp;gt;waiters 리스트에 list_push_back 함수로 맨 뒤에 추가된다. 공유자원의 사용을 마친 스레드가 sema_up 을 하면 thread_unblock 을 하는데, list_pop_front 함수로 sema-&amp;gt;waiters 리스트의 맨 앞에서 꺼내는 것을 볼 수 있다. 이전 포스트에서 봤던 ready_list 와 똑같이 리스트에 들어온 순서대로 차례로 실행되는 방식을 따르는 것을 알 수 있다. ready_list 에서 했던 것처럼 이번에도 priority 에 따라 자원을 할당받도록 만들어보자. 추가할 때는 priority 에 내림차순으로 리스트에 추가하고 꺼낼 때는 그대로 맨 앞에서 꺼내면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1619739947154&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void
sema_down (struct semaphore *sema) 
{
  enum intr_level old_level;

  ASSERT (sema != NULL);
  ASSERT (!intr_context ());

  old_level = intr_disable ();
  while (sema-&amp;gt;value == 0) 
    {
      list_insert_ordered (&amp;amp;sema-&amp;gt;waiters, &amp;amp;thread_current ()-&amp;gt;elem, thread_compare_priority, 0);
      thread_block ();
    }
  sema-&amp;gt;value--;
  intr_set_level (old_level);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;while 문장 안에 있던 list_push_back 을 list_insert_ordered 을 사용하여 내림차순으로 정렬되도록 추가하였다. semaphore 에 추가되는 element 들은 스레드이므로 스레드에서 사용하였던 thread_compare_priority 함수를 그대로 사용하면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1619740068231&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void
sema_up (struct semaphore *sema) 
{
  enum intr_level old_level;

  ASSERT (sema != NULL);

  old_level = intr_disable ();
  if (!list_empty (&amp;amp;sema-&amp;gt;waiters))
  {
    list_sort (&amp;amp;sema-&amp;gt;waiters, thread_compare_priority, 0);
    thread_unblock (list_entry (list_pop_front (&amp;amp;sema-&amp;gt;waiters),
                                struct thread, elem));
  }
  sema-&amp;gt;value++;
  thread_test_preemption ();
  intr_set_level (old_level);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;sema_up 에서는 waiters 리스트에 있던 동안 우선순위에 변경이 생겼을 수도 있으므로 waiters 리스트를 내림차순으로 정렬하여 준다. &lt;b&gt;&amp;lt;lib/kernel/list.c&amp;gt;&lt;/b&gt; 에 있는 list_sort 함수를 사용하면 간단하게 가능하다. 그 후 unblock 된 스레드가 running 스레드보다 우선순위가 높을 수 있으므로 thread_test_preemption () 함수를 실행하여 CPU 선점이 일어나게 해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 semaphore 에 대한 작업은 끝이났다. 다음은 lock 의 구조를 살펴보자.&lt;/p&gt;
&lt;pre id=&quot;code_1619740505555&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* thread/synch.c */
void
lock_acquire (struct lock *lock)
{
  ASSERT (lock != NULL);
  ASSERT (!intr_context ());
  ASSERT (!lock_held_by_current_thread (lock));

  sema_down (&amp;amp;lock-&amp;gt;semaphore);
  lock-&amp;gt;holder = thread_current ();
}

void
lock_release (struct lock *lock) 
{
  ASSERT (lock != NULL);
  ASSERT (lock_held_by_current_thread (lock));

  lock-&amp;gt;holder = NULL;
  sema_up (&amp;amp;lock-&amp;gt;semaphore);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;lock 을 다루는 두 함수를 살펴보면 알 수 있듯이 마지막이 sema_down, sema_up 으로 끝난다. 즉 lock 은 value = 1 이고, holder 정보를 가지고 있다는 것을 제외하고는 semaphore 와 동일하게 동작하기 때문에 sema_down, sema_up 을 이미 처리해주었으므로 lock 은 따로 처리해 줄 필요가 없다는 것을 알 수 있다.&lt;/p&gt;

&lt;h2 data-ke-size=&quot;size26&quot;&gt;Condition variables&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 condition variables 을 다루는 cond_wait, cond_signal 함수를 살펴보자.&lt;/p&gt;
&lt;pre id=&quot;code_1619740709135&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* thread/synch.c */
void
cond_wait (struct condition *cond, struct lock *lock) 
{
  struct semaphore_elem waiter;

  ASSERT (cond != NULL);
  ASSERT (lock != NULL);
  ASSERT (!intr_context ());
  ASSERT (lock_held_by_current_thread (lock));
  
  sema_init (&amp;amp;waiter.semaphore, 0);
  list_push_back (&amp;amp;cond-&amp;gt;waiters, &amp;amp;waiter.elem);
  lock_release (lock);
  sema_down (&amp;amp;waiter.semaphore);
  lock_acquire (lock);
}

void
cond_signal (struct condition *cond, struct lock *lock UNUSED) 
{
  ASSERT (cond != NULL);
  ASSERT (lock != NULL);
  ASSERT (!intr_context ());
  ASSERT (lock_held_by_current_thread (lock));

  if (!list_empty (&amp;amp;cond-&amp;gt;waiters))
  {
    sema_up (&amp;amp;list_entry (list_pop_front (&amp;amp;cond-&amp;gt;waiters),
                          struct semaphore_elem, elem)-&amp;gt;semaphore);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;condition variables 도 마찬가지로 waiters 리스트를 가지고 있는데, semaphore 와 다른 점이 하나가 있다. semaphore 는 waiters 가 스레드들의 리스트였다면 condition variables 의 waiters 는 세마포들의 리스트라는 점이다. cond_wait 함수의 list_push_back 부분을 보면 알 수 있는데, cond-&amp;gt;waiters 리스트에 waiter.elem 을 추가하는데 여기서 waiter 는 함수의 맨 위에서 선언한 세마포 element 이다. 이 점을 유의하면서 진행하여 보자. 이 condition variables 에 묶여있는 여러 세마포들의 리스트 중에서 가장 우선순위가 높은 하나의 세마포를 깨워야 하는데, 이미 각 세마포의 waiters 리스트는 위의 세마포를 다루는 함수에서 내림차순으로 정렬되게 하였으므로 각 세마포의 waiters 리스트의 맨 앞의 element 가 각 세마포에서 가장 priority 가 큰 스레드이고 이들을 비교하여 가장 큰 priority 를 가진 스레드를 가진 세마포를 깨우면 된다.&lt;/p&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;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;여기서도 마찬가지로 list_push_back 함수를 list_insert_ordered 로 바꾸어주는 것이 거의 전부이다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;이번에는 입력받는 파라미터가 스레드가 아니라 semaphore_elem.elem 로 들어오기 때문에 thread_compare_priority 함수는 사용하지 못하고, semaphore_elem 가 나타내는 세마포의 waiters 리스트의 맨 앞 thread 끼리 priority 를 비교하는 함수가 필요하다. (semaphore_elem 구조체는 synch.c 에 선언되어 있다.)&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1619741597150&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* thread/synch.c */
bool 
sema_compare_priority (const struct list_elem *l, const struct list_elem *s, void *aux UNUSED)
{
	struct semaphore_elem *l_sema = list_entry (l, struct semaphore_elem, elem);
	struct semaphore_elem *s_sema = list_entry (s, struct semaphore_elem, elem);

	struct list *waiter_l_sema = &amp;amp;(l_sema-&amp;gt;semaphore.waiters);
	struct list *waiter_s_sema = &amp;amp;(s_sema-&amp;gt;semaphore.waiters);

	return list_entry (list_begin (waiter_l_sema), struct thread, elem)-&amp;gt;priority
		 &amp;gt; list_entry (list_begin (waiter_s_sema), struct thread, elem)-&amp;gt;priority;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;semaphore_elem.elem 을 입력받으면 list_entry 로 semaphore_elem 구조체를 구하여 저장하고 이 구조체가 가지는 semaphore 의 waiters 리스트를 받아온다. 이 waiters 리스트의 맨 앞 element 스레드의 priority 끼리 비교하여 반환하면 된다. 얼핏 복잡해 보이지만 천천히 따라가며 이해해보길 바란다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비교함수를 만들었으니 cond_wait 를 완성할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1619741895067&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void
cond_wait (struct condition *cond, struct lock *lock) 
{
  struct semaphore_elem waiter;

  ASSERT (cond != NULL);
  ASSERT (lock != NULL);
  ASSERT (!intr_context ());
  ASSERT (lock_held_by_current_thread (lock));
  
  sema_init (&amp;amp;waiter.semaphore, 0);
  list_insert_ordered (&amp;amp;cond-&amp;gt;waiters, &amp;amp;waiter.elem, sema_compare_priority, 0);
  lock_release (lock);
  sema_down (&amp;amp;waiter.semaphore);
  lock_acquire (lock);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;list_push_back 대신에 list_insert_ordered 함수에 sema_compare_priority 를 사용해서 가장 높은 priority 를 가지는 스레드가 묶어있는 세마포가 가장 앞으로 오도록 내림차순으로 cond-&amp;gt;waiters 리스트에 push 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1619742007281&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void
cond_signal (struct condition *cond, struct lock *lock UNUSED) 
{
  ASSERT (cond != NULL);
  ASSERT (lock != NULL);
  ASSERT (!intr_context ());
  ASSERT (lock_held_by_current_thread (lock));

  if (!list_empty (&amp;amp;cond-&amp;gt;waiters))
  {
    list_sort (&amp;amp;cond-&amp;gt;waiters, sema_compare_priority, 0);
    sema_up (&amp;amp;list_entry (list_pop_front (&amp;amp;cond-&amp;gt;waiters),
                          struct semaphore_elem, elem)-&amp;gt;semaphore);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;깨울 때도 마찬가지로, wait 도중에 priority 가 바뀌었을 수 있으니 list_sort 로 내림차순으로 정렬해주고, sema_up 은 그대로 맨 앞에서 꺼내도록 둔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;widthContent&quot; data-filename=&quot;스크린샷 2021-04-30 오전 9.21.47.png&quot; data-origin-width=&quot;408&quot; data-origin-height=&quot;502&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yYwPY/btq3O5cEzUV/CB7FKUKZKOLOOBs0vsrTMk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yYwPY/btq3O5cEzUV/CB7FKUKZKOLOOBs0vsrTMk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yYwPY/btq3O5cEzUV/CB7FKUKZKOLOOBs0vsrTMk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyYwPY%2Fbtq3O5cEzUV%2FCB7FKUKZKOLOOBs0vsrTMk%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;408&quot; height=&quot;502&quot; data-filename=&quot;스크린샷 2021-04-30 오전 9.21.47.png&quot; data-origin-width=&quot;408&quot; data-origin-height=&quot;502&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2개의 테스트를 더 통과한 것을 볼 수 있다. 방금 우리가 구현한 priority-sema, priority-condvar 가 pass 가 나왔다. FAIL 의 숫자가 빠르게 줄어가는 것이 보기 좋다. 다음 시간에는 priority-donate 문제를 풀어보도록 한다.&lt;/p&gt;</description>
      <category>프로젝트/Pintos</category>
      <category>condition variables</category>
      <category>lock</category>
      <category>os</category>
      <category>pintos</category>
      <category>semaphore</category>
      <category>Thread</category>
      <category>Threads</category>
      <category>운영체제</category>
      <author>aa_rong_blog</author>
      <guid isPermaLink="true">https://poalim.tistory.com/34</guid>
      <comments>https://poalim.tistory.com/34#entry34comment</comments>
      <pubDate>Fri, 30 Apr 2021 09:23:58 +0900</pubDate>
    </item>
    <item>
      <title>[Pintos] Project 1 : Thread(스레드) - Priority Scheduling(1)</title>
      <link>https://poalim.tistory.com/33</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;스케쥴링은 ready 상태에 있는 스레드들의 순서를 관리하여 가장 높은 priority 를 가진 스레드가 running 상태가 될 수 있도록 만들어주는 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;현재상태&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 현재 pintos 가 스케쥴링을 어떻게 관리하고 있는지 살펴보자. ready_list 에 새로운 스레드가 push 되는 순간을 찾아보면 될 것 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1619715533380&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* thread/thread.c */
void
thread_unblock (struct thread *t) 
{
  enum intr_level old_level;

  ASSERT (is_thread (t));

  old_level = intr_disable ();
  ASSERT (t-&amp;gt;status == THREAD_BLOCKED);
  list_push_back (&amp;amp;ready_list, &amp;amp;t-&amp;gt;elem);	// ready_list 에 스레드 추가
  t-&amp;gt;status = THREAD_READY;
  intr_set_level (old_level);
}

void
thread_yield (void) 
{
  struct thread *cur = thread_current ();
  enum intr_level old_level;
  
  ASSERT (!intr_context ());

  old_level = intr_disable ();
  if (cur != idle_thread) 
    list_push_back (&amp;amp;ready_list, &amp;amp;cur-&amp;gt;elem);	// ready_list 에 스레드 추가
  cur-&amp;gt;status = THREAD_READY;
  schedule ();
  intr_set_level (old_level);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ready_list 에 새로운 스레드가 추가되는 두 함수를 찾아냈다. 공통적으로 &lt;b&gt;list_push_back (&amp;amp;ready_list, ... )&lt;/b&gt; 함수가 사용되었고 이 함수는 &lt;b&gt;&amp;lt;lib/kernel/list.c&amp;gt;&lt;/b&gt; 에서 찾아볼 수 있는데, 이름에서 알 수 있듯이 새로운 elem 을 리스트의 맨 뒤에 push 하는 함수이다. 즉 현재 pintos 는 새로운 스레드를 ready_list 에 넣을 때 항상 맨 뒤에 넣는다는 것을 볼 수 있다. 그럼 ready_list 에서 스레드를 꺼낼 때는 어떻게 진행되는지도 찾아보자. ready_list 에서 스레드를 꺼내는 순간은 현재 running thread 가 CPU 를 양보하여 새로운 running thread 를 고르는 순간이다. 아래 그림을 보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-filename=&quot;스크린샷 2021-04-30 오전 2.06.59.png&quot; data-origin-width=&quot;654&quot; data-origin-height=&quot;422&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ehuxwc/btq3KdQMRMa/x2eboeSXdfXKJ2oBAloK10/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ehuxwc/btq3KdQMRMa/x2eboeSXdfXKJ2oBAloK10/img.png&quot; data-alt=&quot;스레드의 흐름&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ehuxwc/btq3KdQMRMa/x2eboeSXdfXKJ2oBAloK10/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fehuxwc%2Fbtq3KdQMRMa%2Fx2eboeSXdfXKJ2oBAloK10%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;654&quot; height=&quot;422&quot; data-filename=&quot;스크린샷 2021-04-30 오전 2.06.59.png&quot; data-origin-width=&quot;654&quot; data-origin-height=&quot;422&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;위 그림에서 보면 THREAD_RUNNING 에서 나가는 화살표가 running thread 가 CPU 를 양보하는 순간이다. &lt;b&gt;thread_exit(), thread_yield(), thread_block()&lt;/b&gt; 세 경우가 있는 것을 볼 수 있다. 이 세 함수들은 &lt;b&gt;&amp;lt;thread/thread.c&amp;gt;&lt;/b&gt; 에 있고, 하나하나 살펴보면 함수의 마지막부분에 schedule() 함수를 호출하고 CPU 소유권을 내려놓는다. 그럼 이 &lt;b&gt;schedule()&lt;/b&gt; 를 살펴보아야 한다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1619716194543&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* thread/thread.c */
static void
schedule (void) 
{
  struct thread *cur = running_thread ();
  struct thread *next = next_thread_to_run ();
  struct thread *prev = NULL;

  ASSERT (intr_get_level () == INTR_OFF);
  ASSERT (cur-&amp;gt;status != THREAD_RUNNING);
  ASSERT (is_thread (next));

  if (cur != next)
    prev = switch_threads (cur, next);
  thread_schedule_tail (prev);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;schedule() 함수는 switch_threads (cur, next) 함수를 실행하여 CPU 점유권을 cur 에서 next 스레드로 넘긴다. switch_threads 함수의 자세한 설명은 thread background 포스트(&lt;a href=&quot;https://poalim.tistory.com/26&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;poalim.tistory.com/26&lt;/a&gt;)에 설명 해놓았으니 확인해보길 바란다. 그럼 여기서 다음으로 CPU 의 점유권을 받는 함수는 &lt;b&gt;next_thread_to_run()&lt;/b&gt; 에 의해 결정된다는 것을 알 수 있다. 이 함수 역시 thread.c 에서 찾아볼 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1619716400919&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static struct thread *
next_thread_to_run (void) 
{
  if (list_empty (&amp;amp;ready_list))
    return idle_thread;
  else
    return list_entry (list_pop_front (&amp;amp;ready_list), struct thread, elem);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반환값을 보면 list_pop_front (&amp;amp;ready_list), 즉 ready_list 의 맨 앞의 항목을 반환한다는 것을 알 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 현재 pintos 는 ready_list 에 push 는 맨 뒤에, pop 은 맨 앞에서 하는 &lt;b&gt;round-robin&lt;/b&gt; 방식을 채택하고 있다는 것을 알 수 있다. 이러한 방식은 &lt;b&gt;스레드간의 우선순위 없이 ready_list 에 들어온 순서대로 실행&lt;/b&gt;하여 가장 간단하지만 제대로 된 우선순위 스케쥴링이 이루어지고 있지 않다고 할 수 있다.&lt;/p&gt;

&lt;h2 data-ke-size=&quot;size26&quot;&gt;Priority Scheduling 구현&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pintos 가 제대로 우선순위 스케쥴링을 하도록 만들어보자. 두 가지 방법이 가능할 것 같다.&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;ready_list 에 push 할 때 priority 순서에 맞추어 push 하는 방법&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;ready_list 에서 pop 할 때 priority 가 가장 높은 스레드를 찾아 pop 하는 방법&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전자는 항상 ready_list 가 정렬된 상태를 유지하기 때문에 새로운 스레드를 push 할 때 list 의 끝까지 모두 확인할 필요가 없지만, 후자는 ready_list 가 정렬된 상태를 유지하지 않으므로 pop 할 때마다 list 의 처음부터 끝까지 모두 훑어야 하기 때문에 전자가 더 효율적이기 때문에 전자의 방법을 사용하기로 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;push 할 때 순서에 맞추어 넣기 때문에 꺼낼때는 리스트의 맨 앞에서 꺼내는 현재 방식을 유지하고 정렬 방식을 내림차순으로 설정하면 되기 때문에 next_thread_to_run() 함수는 건드리지 않아도 될 것 같다. 바꾸어야 하는 것은 위에서 살펴본 list_push_back 이 실행되는 thread_unblock(), thread_yield() 함수이다. thread_create() 함수에서도 새로운 스레드가 ready_list 에 추가되지만 thread_create() 함수 자체에서 thread_unblock() 함수를 포함하고 있기 때문에 thread_unblock() 함수를 수정함으로써 동시에 처리된다. 그럼 list_push_back 함수를 내림차순으로 정렬하여 push 하는 함수로 대체하여 보자. &lt;b&gt;&amp;lt;lib/kernel/list.c&amp;gt;&lt;/b&gt; 에는 list 를 다루는 여러 함수들이 구현되어 있는데 아주 유용해 보이는 함수를 찾아볼 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1619717368332&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* lib/kernel/list.c */
void
list_insert_ordered (struct list *list, struct list_elem *elem,
                     list_less_func *less, void *aux)
{
  struct list_elem *e;

  ASSERT (list != NULL);
  ASSERT (elem != NULL);
  ASSERT (less != NULL);

  for (e = list_begin (list); e != list_end (list); e = list_next (e))
    if (less (elem, e, aux))
      break;
  return list_insert (e, elem);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이름에서 볼 수 있듯이 정렬하여 리스트에 insert 하는 함수이다. parameter 는 insert 할 list 와 element 를 받고 비교함수를 받는다. 내용을 보면 for 반복문으로 list 의 첫 e 부터 진행하면서 입력받은 elem 이 less 함수를 통과하는 순간의 e 위치에 해당 element 를 삽입한다. e 위치에 삽입하는 list_insert (e, elem) 함수가 elem 을 e 의 앞에 삽입하는지 뒤에 삽입하는지도 확인해야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1619717724463&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* lib/kernel/list.c */
void
list_insert (struct list_elem *before, struct list_elem *elem)
{
  ASSERT (is_interior (before) || is_tail (before));
  ASSERT (elem != NULL);
  elem-&amp;gt;prev = before-&amp;gt;prev;
  elem-&amp;gt;next = before;
  before-&amp;gt;prev-&amp;gt;next = elem;
  before-&amp;gt;prev = elem;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;elem-&amp;gt;next = before 이므로 &lt;b&gt;elem 은 e 의 앞에 삽입&lt;/b&gt;된다는 것을 확인했다. 우리는 ready_list 에서 스레드를 pop 할 때 가장 앞에서 꺼내기로 하였기 때문에 가장 앞에 priority 가 가장 높은 스레드가 와야 한다. 즉, ready_list 는 내림차순으로 정렬되어야 한다. list_insert 가 일어나는 순간 elem 은 e 의 앞으로 들어가기 때문에 내림차순으로 정렬되기 위해서는 if (less (elem, e, aux)) 가 elem &amp;gt; e 인 순간에 break; 를 실행해주어야 한다. 즉, 우리가 만들어야 하는 함수&lt;b&gt; less (elem, e, aux) 는 elem &amp;gt; e 일 때 true 를 반환하는 함수&lt;/b&gt;이다.&lt;/p&gt;
&lt;pre id=&quot;code_1619718162369&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* thread/thread.c */
bool 
thread_compare_priority (struct list_elem *l, struct list_elem *s, void *aux UNUSED)
{
    return list_entry (l, struct thread, elem)-&amp;gt;priority
         &amp;gt; list_entry (s, struct thread, elem)-&amp;gt;priority;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 비교 함수를 만들어 주었다. 이제 이 함수를 적용하여&lt;b&gt; list_push_back 을 list_insert_ordered 로 바꾸어주면 된다&lt;/b&gt;!&lt;/p&gt;
&lt;pre id=&quot;code_1619718348606&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* thread/thread.c */
void
thread_unblock (struct thread *t) 
{
  enum intr_level old_level;

  ASSERT (is_thread (t));

  old_level = intr_disable ();
  ASSERT (t-&amp;gt;status == THREAD_BLOCKED);
  //list_push_back (&amp;amp;ready_list, &amp;amp;t-&amp;gt;elem);
  list_insert_ordered (&amp;amp;ready_list, &amp;amp;t-&amp;gt;elem, thread_compare_priority, 0);
  t-&amp;gt;status = THREAD_READY;
  intr_set_level (old_level);
}

void
thread_yield (void) 
{
  struct thread *cur = thread_current ();
  enum intr_level old_level;
  
  ASSERT (!intr_context ());

  old_level = intr_disable ();
  if (cur != idle_thread) 
    //list_push_back (&amp;amp;ready_list, &amp;amp;cur-&amp;gt;elem);
    list_insert_ordered (&amp;amp;ready_list, &amp;amp;cur-&amp;gt;elem, thread_compare_priority, 0);
  cur-&amp;gt;status = THREAD_READY;
  schedule ();
  intr_set_level (old_level);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;thread_unblock, thread_yield 에서 list_push_back 을 주석처리하고 list_insert_ordered 를 넣어주었다. 세 번째 인자에 위에서 만든 thread_compare_priority 함수를 넣어서 처리해주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자, 아직 한 가지가 남았다. 현재 실행중인 running 스레드의 priority 가 바뀌는 순간이 있다. 이 때 바뀐 priority 가 ready_list 의 가장 높은 priority 보다 낮다면 CPU 점유를 넘겨주어야 한다. 현재 실행중인 스레드의 priority 가 바뀌는 순간은 두 가지 경우이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;thread_create()&lt;/li&gt;
&lt;li&gt;thread_set_priority()&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 두 경우의 마지막에 running thread 와 ready_list 의 가장 앞의 thread 의 priority 를 비교하는 코드를 넣어주어야 한다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1619718790725&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* thread/thread.c */
void 
thread_test_preemption (void)
{
    if (!list_empty (&amp;amp;ready_list) &amp;amp;&amp;amp; 
    thread_current ()-&amp;gt;priority &amp;lt; 
    list_entry (list_front (&amp;amp;ready_list), struct thread, elem)-&amp;gt;priority)
        thread_yield ();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;running thread 와 ready_list 의 가장 앞 thread 의 priority 를 비교하고, 만약 ready_list 의 스레드가 더 높은 priority 를 가진다면 thread_yield () 를 실행하여 CPU 점유권을 넘겨주는 함수를 만들었다. 이 함수를 thread_create() 와 thread_set_priority() 에 추가하여 준다.&lt;/p&gt;
&lt;pre id=&quot;code_1619718951110&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* thread/thread.c */
tid_t
thread_create (const char *name, int priority,
               thread_func *function, void *aux) 
{
  ...
  thread_unblock (t);
  thread_test_preemption ();

  return tid;
}

void
thread_set_priority (int new_priority) 
{
  thread_current ()-&amp;gt;priority = new_priority;
  thread_test_preemption ();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결과&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 기본적인 priority scheduling 을 구현하였으니 테스트를 해보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-filename=&quot;스크린샷 2021-04-30 오전 2.58.30.png&quot; data-origin-width=&quot;430&quot; data-origin-height=&quot;500&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/clSMSR/btq3PQsIwQV/DZkh7AdG2EKnsgkDqjNANK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/clSMSR/btq3PQsIwQV/DZkh7AdG2EKnsgkDqjNANK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/clSMSR/btq3PQsIwQV/DZkh7AdG2EKnsgkDqjNANK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FclSMSR%2Fbtq3PQsIwQV%2FDZkh7AdG2EKnsgkDqjNANK%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;500&quot; data-filename=&quot;스크린샷 2021-04-30 오전 2.58.30.png&quot; data-origin-width=&quot;430&quot; data-origin-height=&quot;500&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무려 4개의 테스트를 더 통과했다!! 아직 통과하지 못한 테스트를 보면 priority-donate, priority-sema 등의 항목이 눈에 띈다. pintos 도큐먼트에서도 명시되어 있듯이 priority scheduling 은 priority donate 와 priority sema 까지 구현해야 한다. 이것은 다음 포스트에서 살펴보도록 한다.&lt;/p&gt;</description>
      <category>프로젝트/Pintos</category>
      <category>os</category>
      <category>pintos</category>
      <category>priority scheduling</category>
      <category>System Programming</category>
      <category>Thread</category>
      <category>운영체제</category>
      <author>aa_rong_blog</author>
      <guid isPermaLink="true">https://poalim.tistory.com/33</guid>
      <comments>https://poalim.tistory.com/33#entry33comment</comments>
      <pubDate>Fri, 30 Apr 2021 03:02:39 +0900</pubDate>
    </item>
    <item>
      <title>[비트코인] 가상화폐 자동투자 프로그램 개발하기(5) - slack 알람 봇</title>
      <link>https://poalim.tistory.com/32</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스트에서는 자동투자 프로그램이 매매를 진행할 때마다 메신저에서 알람을 보내주도록 해본다. 메신저는 API 및 다른 프로그램 연결이 용이한 Slack 을 사용하여 진행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 slack 사용을 위한 기본 세팅을 해주자.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Slack&amp;nbsp; 가입 및 워크스페이스 생성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 &lt;a href=&quot;https://slack.com/intl/ko-kr/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://slack.com&lt;/a&gt; 아래 슬랙 홈페이지에 들어간다.&lt;/p&gt;
&lt;figure id=&quot;og_1619534256642&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;새 HQ에 오신 것을 환영합니다.&quot; data-og-description=&quot;Slack은 여러분의 팀과 소통할 새로운 방법입니다. 이메일보다 빠르고, 더 조직적이며, 훨씬 안전합니다.&quot; data-og-host=&quot;slack.com&quot; data-og-source-url=&quot;https://slack.com/intl/ko-kr/&quot; data-og-url=&quot;https://slack.com/intl/ko-kr/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ekPiNz/hyJ2nhKAd7/3HrxCrJqQJGvqXNFY0hYWk/img.jpg?width=2400&amp;amp;height=1256&amp;amp;face=408_202_1919_918,https://scrap.kakaocdn.net/dn/bjA81E/hyJ0C8FZJ4/uvZZHQHoMKfdVQGDSiWBF0/img.jpg?width=2400&amp;amp;height=1256&amp;amp;face=408_202_1919_918,https://scrap.kakaocdn.net/dn/lykSz/hyJ2u2fGOB/Ik2R5vLlBkmqPBkU8TOYk1/img.png?width=641&amp;amp;height=305&amp;amp;face=11_193_71_259&quot;&gt;&lt;a href=&quot;https://slack.com/intl/ko-kr/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://slack.com/intl/ko-kr/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ekPiNz/hyJ2nhKAd7/3HrxCrJqQJGvqXNFY0hYWk/img.jpg?width=2400&amp;amp;height=1256&amp;amp;face=408_202_1919_918,https://scrap.kakaocdn.net/dn/bjA81E/hyJ0C8FZJ4/uvZZHQHoMKfdVQGDSiWBF0/img.jpg?width=2400&amp;amp;height=1256&amp;amp;face=408_202_1919_918,https://scrap.kakaocdn.net/dn/lykSz/hyJ2u2fGOB/Ik2R5vLlBkmqPBkU8TOYk1/img.png?width=641&amp;amp;height=305&amp;amp;face=11_193_71_259');&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;새 HQ에 오신 것을 환영합니다.&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Slack은 여러분의 팀과 소통할 새로운 방법입니다. 이메일보다 빠르고, 더 조직적이며, 훨씬 안전합니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;slack.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오른쪽 상단의 '로그인' 을 눌러서 계정을 생성하고 로그인을 해준다. 보유중인 이메일을 가지고 간편하게 회원가입 및 로그인이 가능하다. &lt;span style=&quot;color: #333333;&quot;&gt;로그인이 되면 워크스페이스를 생성할 수 있는 창이 나오는데, '워크스페이스 생성'을 클릭한 후 &lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;워크 스페이스 이름, 채널 이름 등을 입력하여 주면 새로운 워크 스페이스가 생성된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Slack API 발급&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 slack 과 프로그램의 소통을 가능하게 해주는 slack API 를 발급받아야 한다. &lt;a href=&quot;https://api.slack.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;api.slack.com&lt;/a&gt; 에 접속하여 Create a custom app 이라는 초록색 버튼을 클릭하여 app 을 생성하여 주도록 하자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;1339&quot; data-origin-height=&quot;350&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cnyw46/btq3AQnzu0k/JoilCoMA5GTPbK93KEblH0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cnyw46/btq3AQnzu0k/JoilCoMA5GTPbK93KEblH0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cnyw46/btq3AQnzu0k/JoilCoMA5GTPbK93KEblH0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcnyw46%2Fbtq3AQnzu0k%2FJoilCoMA5GTPbK93KEblH0%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;1339&quot; height=&quot;350&quot; data-origin-width=&quot;1339&quot; data-origin-height=&quot;350&quot;/&gt;&lt;/span&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같은 창이 나오는데, App Name 은 임의로 입력하여 주고 아래 Development Slack Workspace 창에서는 아까 우리가 만든 workspace 를 선택하여 준 후 Create App 버튼을 눌러 생성하여 준다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;689&quot; data-origin-height=&quot;543&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnLCSZ/btq3yK9qFOc/EcAXDcwZEhWlLJPSPGM4WK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnLCSZ/btq3yK9qFOc/EcAXDcwZEhWlLJPSPGM4WK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnLCSZ/btq3yK9qFOc/EcAXDcwZEhWlLJPSPGM4WK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbnLCSZ%2Fbtq3yK9qFOc%2FEcAXDcwZEhWlLJPSPGM4WK%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;510&quot; height=&quot;543&quot; data-origin-width=&quot;689&quot; data-origin-height=&quot;543&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성이 완료되면 App 의 설정창이 나오는데, 왼쪽의 리스트에서 OAuth &amp;amp; Permission 메뉴에 들어가 준 후&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-filename=&quot;화면 캡처 2021-04-27 235823.jpg&quot; data-origin-width=&quot;324&quot; data-origin-height=&quot;673&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5DSue/btq3GCuzbIu/seOAdBNcsgsTgZEYdJLh8K/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5DSue/btq3GCuzbIu/seOAdBNcsgsTgZEYdJLh8K/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5DSue/btq3GCuzbIu/seOAdBNcsgsTgZEYdJLh8K/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5DSue%2Fbtq3GCuzbIu%2FseOAdBNcsgsTgZEYdJLh8K%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;236&quot; height=&quot;673&quot; data-filename=&quot;화면 캡처 2021-04-27 235823.jpg&quot; data-origin-width=&quot;324&quot; data-origin-height=&quot;673&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Scopes 의 Bot Token Scopes 에서 Add an OAuth Scope 를 클릭 후 chat:write 기능을 추가해준다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;690&quot; data-origin-height=&quot;325&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFpBfn/btq3z4l4s79/yLqOGtHqoPknToPky9cuK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFpBfn/btq3z4l4s79/yLqOGtHqoPknToPky9cuK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFpBfn/btq3z4l4s79/yLqOGtHqoPknToPky9cuK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFpBfn%2Fbtq3z4l4s79%2FyLqOGtHqoPknToPky9cuK0%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;590&quot; height=&quot;325&quot; data-origin-width=&quot;690&quot; data-origin-height=&quot;325&quot;/&gt;&lt;/span&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;그 후 맨 위에서 Install to Workspace 버튼을 누르면 &lt;span&gt;Bot User OAuth Token 라는 토큰이 발행된다. 이 값을 복사하여 보관하여 두자.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;1052&quot; data-origin-height=&quot;406&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bNOIdm/btq3DsT04Ks/3Vwn1Pl2CbWmvdYnZMG2kk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNOIdm/btq3DsT04Ks/3Vwn1Pl2CbWmvdYnZMG2kk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNOIdm/btq3DsT04Ks/3Vwn1Pl2CbWmvdYnZMG2kk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNOIdm%2Fbtq3DsT04Ks%2F3Vwn1Pl2CbWmvdYnZMG2kk%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;611&quot; height=&quot;406&quot; data-origin-width=&quot;1052&quot; data-origin-height=&quot;406&quot;/&gt;&lt;/span&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;또한 이렇게 생성한 App 을 우리가 만든 채팅방에 연결해주어야 한다. 아까 만들었던 워크스페이스 채팅화면으로 들어가서 가장 오른쪽 상단에 세부정보(i) -&amp;gt; 더보기 -&amp;gt; 앱추가 -&amp;gt; api.slack 에서 생성한 app 추가 해준다.&lt;/p&gt;

&lt;h2 data-ke-size=&quot;size26&quot;&gt;파이썬 프로그램에 slack 연결&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 파이썬에서 post request 를 slack 으로 보낼 것이다. 우선 &lt;b&gt;pip install request&lt;/b&gt; 를 터미널에 쳐서 request 모듈을 설치하여 준다.&lt;/p&gt;
&lt;pre id=&quot;code_1619536155871&quot; class=&quot;python&quot; style=&quot;display: block; overflow: auto; padding: 15px; color: #383a42; background: #f6f7f8; font-size: 14px; border-radius: 3px; font-family: Menlo, Consolas, Monaco, monospace; border: 1px solid #dddddd; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import requests
 
def post_message(token, channel, text):
    response = requests.post(&quot;https://slack.com/api/chat.postMessage&quot;,
        headers={&quot;Authorization&quot;: &quot;Bearer &quot;+token},
        data={&quot;channel&quot;: channel,&quot;text&quot;: text}
    )
    print(response)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 위와 같은 코드를 자동매매 코드에 추가한다. 발급받은 api token, 메세지를 발송할 channel 명, 보낼 text 를 slack api 에 post request 로 보내주는 함수이다. 편의를 위해 아래와 같이 발급받은 token 과 channel 명을 함수에 포함시켜도 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1619536473087&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import requests

def post_message(text):
    myToken = `your token`
    channel = `your channel`
    response = requests.post(&quot;https://slack.com/api/chat.postMessage&quot;,
        headers={&quot;Authorization&quot;: &quot;Bearer &quot;+myToken},
        data={&quot;channel&quot;: channel,&quot;text&quot;: text}
    )
    print(response)
&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;이렇게 만들었으면 아래와 같이 post_message 함수에 전송할 메세지만을 담아서 사용할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1619536361965&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;post_message(&quot;Message Here!&quot;)
&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;자동매매 프로그램에서 slack 메신저로 받고 싶은 메시지를 자유롭게 추가하여 주자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;584&quot; data-origin-height=&quot;28&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bk0y2Y/btq3GC2pqws/bpNJUOnSdg2C6NM4LGBg6K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bk0y2Y/btq3GC2pqws/bpNJUOnSdg2C6NM4LGBg6K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bk0y2Y/btq3GC2pqws/bpNJUOnSdg2C6NM4LGBg6K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbk0y2Y%2Fbtq3GC2pqws%2FbpNJUOnSdg2C6NM4LGBg6K%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;584&quot; height=&quot;28&quot; data-origin-width=&quot;584&quot; data-origin-height=&quot;28&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;577&quot; data-origin-height=&quot;285&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLzdlh/btq3EIB7pkT/M28JTEFH8DJtEh1t3Dkqm1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLzdlh/btq3EIB7pkT/M28JTEFH8DJtEh1t3Dkqm1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLzdlh/btq3EIB7pkT/M28JTEFH8DJtEh1t3Dkqm1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLzdlh%2Fbtq3EIB7pkT%2FM28JTEFH8DJtEh1t3Dkqm1%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;577&quot; height=&quot;285&quot; data-origin-width=&quot;577&quot; data-origin-height=&quot;285&quot;/&gt;&lt;/span&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자는 이렇게 프로그램이 시작되었을 때 시작 금액을 전송하고, buy 와 sell 주문이 실행됐을 때 체결 및 단가 정보를 전송하도록 하였다.&lt;/p&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;widthContent&quot; data-origin-width=&quot;294&quot; data-origin-height=&quot;72&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bsKzMl/btq3DGj90gx/taA6fj8xW1bdu3ZHB2GGv0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bsKzMl/btq3DGj90gx/taA6fj8xW1bdu3ZHB2GGv0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bsKzMl/btq3DGj90gx/taA6fj8xW1bdu3ZHB2GGv0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbsKzMl%2Fbtq3DGj90gx%2FtaA6fj8xW1bdu3ZHB2GGv0%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;294&quot; height=&quot;72&quot; data-origin-width=&quot;294&quot; data-origin-height=&quot;72&quot;/&gt;&lt;/span&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;완성 코드는 &lt;a href=&quot;https://github.com/poArlim/crypto-auto&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;github.com/poArlim/crypto-auto&lt;/a&gt; 의 cryptoAutoTrade_v3.py 에서 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프로젝트/가상화폐 자동투자</category>
      <category>Slack</category>
      <category>가상화폐</category>
      <category>리플</category>
      <category>비트코인</category>
      <category>이더리움</category>
      <category>자동매매</category>
      <category>파이썬</category>
      <author>aa_rong_blog</author>
      <guid isPermaLink="true">https://poalim.tistory.com/32</guid>
      <comments>https://poalim.tistory.com/32#entry32comment</comments>
      <pubDate>Wed, 28 Apr 2021 00:31:37 +0900</pubDate>
    </item>
    <item>
      <title>[비트코인] 가상화폐 자동투자 프로그램 개발하기(4) - 변동성 돌파 전략 보완(version 2)</title>
      <link>https://poalim.tistory.com/31</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;지난 2장에서는 변동성 돌파 전략의 설명과 백테스팅을 진행하여 보았다. 아직 읽어보지 않았다면 아래 이전 2장 포스트를 먼저 읽고 오는 것을 추천한다.&lt;/p&gt;
&lt;figure id=&quot;og_1619510088805&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[비트코인] 가상화폐 자동투자 프로그램 개발하기(2) - 변동성 돌파 전략 및 백테스팅&quot; data-og-description=&quot;이번 시간에는 가상화폐 자동투자를 실행할 '전략'에 대해 포스팅한다. 사람이 직접 하는 투자가 아니라 프로그램이 자동으로 진행하는 투자이기 때문에, 최대한 체계적이고 일률적인 투자전략&quot; data-og-host=&quot;poalim.tistory.com&quot; data-og-source-url=&quot;https://poalim.tistory.com/29&quot; data-og-url=&quot;https://poalim.tistory.com/29&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/FEfi9/hyJ2t28Pxg/OioVmyhWKgiA96A2KdcSRk/img.png?width=573&amp;amp;height=108&amp;amp;face=0_0_573_108,https://scrap.kakaocdn.net/dn/bB2KGb/hyJ0E6kCIv/TjsxMWkDh1Im9kk1DJ5vUK/img.png?width=573&amp;amp;height=108&amp;amp;face=0_0_573_108,https://scrap.kakaocdn.net/dn/iCFn1/hyJ2pNc0Fl/K1ibxUByPqRyXQcitofkFk/img.png?width=962&amp;amp;height=796&amp;amp;face=0_0_962_796&quot;&gt;&lt;a href=&quot;https://poalim.tistory.com/29&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://poalim.tistory.com/29&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/FEfi9/hyJ2t28Pxg/OioVmyhWKgiA96A2KdcSRk/img.png?width=573&amp;amp;height=108&amp;amp;face=0_0_573_108,https://scrap.kakaocdn.net/dn/bB2KGb/hyJ0E6kCIv/TjsxMWkDh1Im9kk1DJ5vUK/img.png?width=573&amp;amp;height=108&amp;amp;face=0_0_573_108,https://scrap.kakaocdn.net/dn/iCFn1/hyJ2pNc0Fl/K1ibxUByPqRyXQcitofkFk/img.png?width=962&amp;amp;height=796&amp;amp;face=0_0_962_796');&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;[비트코인] 가상화폐 자동투자 프로그램 개발하기(2) - 변동성 돌파 전략 및 백테스팅&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;poalim.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;백테스팅 결과는 가상화폐를 처음부터 끝까지 그냥 들고있었을 때보다 &lt;b&gt;수익률과 MDD 모두 우수&lt;/b&gt;했다. 오늘(2021/04/27 17:00) 기준 백테스팅 결과를 다시 한 번 확인해 보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;626&quot; data-origin-height=&quot;40&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3AxTu/btq3znlohf8/mmea7h13fkHM4W9SPUwC1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3AxTu/btq3znlohf8/mmea7h13fkHM4W9SPUwC1k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3AxTu/btq3znlohf8/mmea7h13fkHM4W9SPUwC1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3AxTu%2Fbtq3znlohf8%2Fmmea7h13fkHM4W9SPUwC1k%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;626&quot; height=&quot;40&quot; data-origin-width=&quot;626&quot; data-origin-height=&quot;40&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무려 2배 가까운 수익률을 얻을 수 있었다. 하지만 변동성 돌파 전략의 장점은 수익률 뿐 아니라 최대 손실률(MDD) 가 낮아서 &lt;b&gt;안정적으로 우상향 한다&lt;/b&gt;는 데 있다. 백테스팅 결과를 그래프로 확인해 보면 이 점이 더욱 명확해진다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;1194&quot; data-origin-height=&quot;654&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/B8XWh/btq3Evh0Vdy/b0rLqEkecuBcO0nOcIInXk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/B8XWh/btq3Evh0Vdy/b0rLqEkecuBcO0nOcIInXk/img.png&quot; data-alt=&quot;2017-10-06 - 2021-04-27 기간의 비트코인 가격 변동 차트&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/B8XWh/btq3Evh0Vdy/b0rLqEkecuBcO0nOcIInXk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FB8XWh%2Fbtq3Evh0Vdy%2Fb0rLqEkecuBcO0nOcIInXk%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;730&quot; height=&quot;654&quot; data-origin-width=&quot;1194&quot; data-origin-height=&quot;654&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;2017-10-06 - 2021-04-27 기간의 비트코인 가격 변동 차트&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;1125&quot; data-origin-height=&quot;621&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bIoUd0/btq3xmHAwVm/0ZBvHLrK1eoHDhALHd66K0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bIoUd0/btq3xmHAwVm/0ZBvHLrK1eoHDhALHd66K0/img.png&quot; data-alt=&quot;같은 기간 변동성 돌파 전략을 적용하였을 때의 수익률 차트&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bIoUd0/btq3xmHAwVm/0ZBvHLrK1eoHDhALHd66K0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbIoUd0%2Fbtq3xmHAwVm%2F0ZBvHLrK1eoHDhALHd66K0%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;724&quot; height=&quot;621&quot; data-origin-width=&quot;1125&quot; data-origin-height=&quot;621&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;b&gt;하지만, 여전히 아쉬움은 존재한다.&lt;/b&gt; 가상화폐의 가격이 오르기만 하는 상승장에서는 돌파신호, 즉 targetPrice 를 만나기까지의 수익률을 포기하여야 하고 그렇다고 K 의 값을 낮추면 리스크 관리 측면에서 좋지 않다. 이러한 아쉬움을 해결하기 위하여 한 가지 전략을 추가하여 보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;최적의 K 구하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 우리의 전략은 전체 기간의 백테스팅을 통하여 가장 좋은 결과를 낸 K = 0.5 값을 모든 구간에서 적용하고 있다. 하지만, &lt;b&gt;어느 구간이냐에 따라 가장 좋은 결과를 내는 K 의 값은 달라지기 때문에 구간별로 최적의 K 값을 적용한 targetPrice 를 적용하면 더 좋은 수익률을 얻을 수 있을 것이라 생각했다.&lt;/b&gt; 예를 들어, 지속되는 상승장에서는 K 값을 최대한 낮게 가져가야 모든 상승분을 얻을 수 있고, 지속되는 하락장에서는 높은 K 값을 적용하여 매수를 최대한 신중하게 진행하도록 할 수 있다. 그렇다면 백테스팅을 통하여 이를 검증하여 보도록 하자.&lt;/p&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;b&gt;오늘의 가격 변동은 가장 최근 가격 변동에 영향을 받기 때문에&lt;/b&gt; 특정한 날에 적용할 &lt;b&gt;최적의 K 값은 최근 20일간의 데이터에서 가장 좋은 수익률을 보인 K 값으로 선정한다.&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1619512803757&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def get_crr(df, fees, K) :
    df['range'] = df['high'].shift(1) - df['low'].shift(1)
    df['targetPrice'] = df['open'] + df['range'] * K
    df['drr'] = np.where(df['high'] &amp;gt; df['targetPrice'], 
    		(df['close'] / (1 + fees)) / (df['targetPrice'] * (1 + fees)), 1)
    return df['drr'].cumprod()[-2]

def get_best_K(coin, fees, to) :
    df = pyupbit.get_ohlcv(coin, to = to, interval = &quot;day&quot;, count = 21)
    time.sleep(0.1)
    max_crr = 0
    best_K = 0.5
    for k in np.arange(0.0, 1.0, 0.1) :
        crr = get_crr(df, fees, k)
        if crr &amp;gt; max_crr :
            max_crr = crr
            best_K = k
    return best_K&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;get_crr()&lt;/b&gt; 함수는 입력받은 데이터에서 수익률을 계산하여 기간동안의 누적 수익률을 반환하는 함수이다. 세부 내용은 2장에서 설명하였으니 넘어가도록 하고, 마지막 반환값 return df['drr'].cumprod()[-2] 에서 [-1] 값이 아닌 [-2] 를 해 준 이유는 [-1] 은 입력받은 데이터에서의 가장 마지막 날까지의 누적 수익률인데, 데이터의 가장 마지막 날은 매매를 진행하여야 하는 당일로 아직 매매를 진행하지 않았으므로 하루 전날까지의 누적 수익률을 받아오기 위함이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;get_best_K()&lt;/b&gt; 함수는 특정 날을 기준으로 지난 20일간의 데이터로 최적의 K 값을 찾아 반환하는 함수이다. to 파라미터에 날짜가 들어오면 업비트 API 에서 해당 날짜까지 최근 21일간의 차트 데이터를 받아온다. 가장 최근일을 제외하고 20일간의 데이터를 사용하여야 하므로 21 개의 데이터를 받아와야 한다. 그 후 k 의 값에 0.0 ~ 0.9 까지의 값을 각각 적용하여 누적 수익률을 계산한 후 가장 높은 수익률을 보이는 K 값을 반환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 새로 구한 best_K 값을 적용하여 targetPrice 를 구한다.&lt;/p&gt;
&lt;pre id=&quot;code_1619515668052&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;df['best_K'] = 0.5
for i in range(1, day_count) :
    print(i)
    df['best_K'][i] = get_best_K(coin, fees, df.index[i])
df['targetPrice'] = df['open'] + df['range'] * df['best_K']&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2장에서 만들었던 백테스팅 코드의 targetPrice 를 계산하는 부분을 이렇게 바꾸었다. df.index[i] 는 해당일의 날짜를 반환한다. 전체 코드는 &lt;a href=&quot;https://github.com/poArlim/crypto-auto&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;github.com/poArlim/crypto-auto&lt;/a&gt; 의 backtesting_v3.py 에서 확인 가능하다.&amp;nbsp;&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 요청이 많아서 그런지 생각보다 실행시간이 오래 걸려서 2~3분 가량 걸린 것 같다. 우선 1300일간의 데이터에 일괄 K = 0.5 값을 적용했을 때의 결과는 아래와 같았다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;1158&quot; data-origin-height=&quot;693&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HP6KB/btq3yTrxUUx/cpTJtK7p9nsc9FakoFvII1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HP6KB/btq3yTrxUUx/cpTJtK7p9nsc9FakoFvII1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HP6KB/btq3yTrxUUx/cpTJtK7p9nsc9FakoFvII1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHP6KB%2Fbtq3yTrxUUx%2FcpTJtK7p9nsc9FakoFvII1%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;1158&quot; height=&quot;693&quot; data-origin-width=&quot;1158&quot; data-origin-height=&quot;693&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시작일인 2017-10-06 일 시가에 비트코인을 매수하여 현재 시간인 2021-04-27 까지 보유하였을 때의 수익률은 &lt;b&gt;1198.32%&lt;/b&gt; 였지만, K = 0.5 의 변동성 돌파 전략을 적용하였을 때에는 같은 기간 &lt;b&gt;2098.43%&lt;/b&gt; 의 수익률과 -20% 의 MDD 가 나왔다. 다음은 기간에 따른 최적의 K 를 적용한 변동성 돌파 전략의 백테스팅 결과를 보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;1159&quot; data-origin-height=&quot;710&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cYy0cn/btq3DKUa3Va/tZnDrOWSJwUFR77n6oeKPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cYy0cn/btq3DKUa3Va/tZnDrOWSJwUFR77n6oeKPk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cYy0cn/btq3DKUa3Va/tZnDrOWSJwUFR77n6oeKPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcYy0cn%2Fbtq3DKUa3Va%2FtZnDrOWSJwUFR77n6oeKPk%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;1159&quot; height=&quot;710&quot; data-origin-width=&quot;1159&quot; data-origin-height=&quot;710&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 기간 수익률이 무려 &lt;b&gt;8185.77%&amp;nbsp;&lt;/b&gt;로 나타난다! 생각보다 높은 수치에 놀라서 모든 날짜에 적용된 best_K 값을 확인해보니 best_K 값이 최소치인 0 으로 적용된 날과 최대치인 0.9 로 적용된 날이 생각보다 많다는 것이 눈에 띄었다. 상승 추세가 강할수록 모든 상승분을 챙기고 하락 추세가 강할수록 매수를 최대한으로 자제하여 놀라운 결과를 얻은 것이다. 다만 갑작스러운 추세의 전환에는 약하여 꽤 높은 MDD(-39%) 를 보인 것은 아쉽게 느껴진다.&lt;/p&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;이제 오늘 다룬 version 2 전략을 구현하여 실제 자동매매를 일정 기간 진행해 본 후 결과와 필요하다면 새로운 기능을 추가하여 다음 포스트로 찾아와 보려고 한다. 기다려주세요~!&lt;/p&gt;</description>
      <category>프로젝트/가상화폐 자동투자</category>
      <category>가상화폐</category>
      <category>리플</category>
      <category>변동성돌파전략</category>
      <category>비트코인</category>
      <category>업비트</category>
      <category>이더리움</category>
      <category>자동매매</category>
      <author>aa_rong_blog</author>
      <guid isPermaLink="true">https://poalim.tistory.com/31</guid>
      <comments>https://poalim.tistory.com/31#entry31comment</comments>
      <pubDate>Tue, 27 Apr 2021 18:59:37 +0900</pubDate>
    </item>
    <item>
      <title>[비트코인] 가상화폐 자동투자 프로그램 개발하기(3) - version 1</title>
      <link>https://poalim.tistory.com/30</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;b&gt;targetPrice&lt;/b&gt; 가 필요하기 때문에 이를 구하는 함수를 작성한다.&lt;/p&gt;
&lt;pre id=&quot;code_1619442321153&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def get_targetPrice(df, K) :
    range = df['high'][-2] - df['low'][-2]
    return df['open'][-1] + range * K&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;df 는 pyupbit 의 get_ohlcv() 함수로 받아온 차트 데이터로, 가장 최신(오늘) 의 데이터가 마지막에 저장되어 있으므로 인덱스 -1 은 오늘, -2 는 어제의 데이터를 나타낸다. 전일 고가와 저가의 차이를 구하여 range 를 구하고, 당일 시가에 range * K 값을 더하여 targetPrice 를 구하여 반환한다.&lt;/p&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 id=&quot;code_1619442721028&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def buy_all(coin) :
    balance = upbit.get_balance(&quot;KRW&quot;)
    if balance &amp;gt; 5000 :
    	upbit.buy_market_order(coin, balance)

def sell_all(coin) :
    balance = upbit.get_balance(coin)
    price = pyupbit.get_current_price(coin)
    if price * balance &amp;gt; 5000 :
        upbit.sell_market_order(coin, balance)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;buy_all()&lt;/b&gt; 함수는 현재 가지고 있는 krw 잔액을 모두 사용하여 시장가로 특정 가상화폐를 매수한다. &lt;b&gt;sell_all()&lt;/b&gt; 함수는 반대로 가지고 있는 특정 가상화폐를 전량 시장가 매도한다. 이 때, 업비트 거래소는 최소 주문 금액으로 5000원이라는 금액을 설정하고 있기 때문에 이를 만족할 때에만 주문이 실행되도록 하였다.&lt;/p&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 id=&quot;code_1619443515259&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import pyupbit
import datetime
import time, calendar

# Function Definition #

if __name__ == '__main__': 
    try:
	# Code Here #
    
    except Exception as e:
        print(e)
        time.sleep(1)&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;try 의 시작지점에서 변수들을 설정해준다.&lt;/p&gt;
&lt;pre id=&quot;code_1619443678327&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;upbit = pyupbit.Upbit(`access key`, `secret key`)

coin = &quot;KRW-BTC&quot;	# 매매를 진행할 가상화폐의 ticker
fees = 0.0005		# 수수료
K = 0.5			# targetPrice 를 구할 때 사용할 K 값&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서는 비트코인을 이용한 매매를 0.05%의 수수료와 0.5의 K 값으로 진행하도록 하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1619444090944&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;df = pyupbit.get_ohlcv(coin, count = 2, interval = &quot;day&quot;)
targetPrice = get_targetPrice(df, K)

while True :
    now = datetime.datetime.now()
    if now.hour == 9 and now.minute == 0 and now.second == 0:
        sell_all(coin)
        df = pyupbit.get_ohlcv(coin, count = 2, interval = &quot;day&quot;)
        targetPrice = get_targetPrice(df, K)
    if targetPrice &amp;lt;= pyupbit.get_current_price(coin) :
    	buy_all(coin)
    time.sleep(1)&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 시작 지점에서 차트를 한 번 불러온 후 첫 targetPrice 를 설정하여 주고 while 문장으로 들어간다. 반복문을 계속해서 돌면서 현재 가상화폐의 가격이 targetPrice 보다 높아졌는지를 체크하고, 이를 만족하면 전액 매수한다. 매일 am 09:00 은 새로운 일봉이 시작되는 시간이므로 가지고 있는 가상화폐를 전량 매도 후 차트 데이터와 targetPrice 를 새로 업데이트 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지 구현이 되었다면 이미 정상적으로 실행이 되지만, 프로그램의 효율을 위해 한 가지를 추가해주도록 하자. 가상화폐의 현재가격이 targetPrice 를 돌파하여서 매수가 진행되고 나면, 다음날 시가에 이를 매도하기 전까지는 아무런 작업도 할 필요가 없기 때문에 이 시간동안 프로그램을 쉬도록 해준다.&lt;/p&gt;
&lt;pre id=&quot;code_1619444680589&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if targetPrice &amp;lt;= pyupbit.get_current_price(coin) :
    buy_all(coin)
    start_time = df.index[-1]
    end_time = start_time + datetime.timedelta(days=1)
    time.sleep((end_time - now).seconds - 10)&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;두 번째 if 문장에 3줄을 추가한 코드이다. df.index[-1] 은 차트상 가장 최근(오늘) 데이터의 인덱스를 출력한다. 인덱스는 Datetime 형식으로 당일 장 시작시간이 들어있다. 즉 start_time 변수에는 오늘 장이 시작한 시간(am 09:00)의 시간 데이터가 저장된다. end_time 은 여기에 days=1 을 더한 값으로 다음날 am 09:00 값이다. end_time 에서 현재 시간인 now 를 빼면 다음날 장 시작시간까지 남은 시간이 나오고 이를 초로 환산하여 time.sleep 하여 다음날 장 시작까지 프로그램을 멈춰준다.&lt;/p&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 id=&quot;code_1619445513087&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import pyupbit
import datetime
import time, calendar

def get_targetPrice(df, K) :
    range = df['high'][-2] - df['low'][-2]
    return df['open'][-1] + range * K

def buy_all(coin) :
    balance = upbit.get_balance(&quot;KRW&quot;)
    if balance &amp;gt;= 5000 :
        print(upbit.buy_market_order(coin, balance))

def sell_all(coin) :
    balance = upbit.get_balance(coin)
    price = pyupbit.get_current_price(coin)
    if price * balance &amp;gt;= 5000 :
        print(upbit.sell_market_order(coin, balance))


if __name__ == '__main__': 
    try:
        upbit = pyupbit.Upbit(`access key`, `secret key`)

        coin = &quot;KRW-BTC&quot;
        fees = 0.0005
        K = 0.5
    
        df = pyupbit.get_ohlcv(coin, count = 2, interval = &quot;day&quot;)
        targetPrice = get_targetPrice(df, K)

        while True :
            now = datetime.datetime.now()
            if now.hour == 9 and now.minute == 0 and now.second == 0:
                sell_all(coin)
                df = pyupbit.get_ohlcv(coin, count = 2, interval = &quot;day&quot;)
                targetPrice = get_targetPrice(df, K)             
            
            if targetPrice &amp;lt;= pyupbit.get_current_price(coin) :
                buy_all(coin)
                start_time = df.index[-1]
                end_time = start_time + datetime.timedelta(days=1)
                time.sleep((end_time - now).seconds)
            time.sleep(1)

    except Exception as e:
        print(e)
        time.sleep(1)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;생각보다 간단한 코드가 완성되었다. 실제 코드의 구동 및 백그라운드 실행방법은 &lt;a href=&quot;https://github.com/poArlim/crypto-auto&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;github.com/poArlim/crypto-auto&lt;/a&gt; 의 README.md 를 참고하면 되고, 위 코드또한 cryptoAutoTrade_v1.py 라는 파일로 저장되어 있다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;다음 포스트는 아마 매매 전략의 보완 혹은 메신저 알림 기능을 추가해볼까 한다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>프로젝트/가상화폐 자동투자</category>
      <category>가상화폐</category>
      <category>리플</category>
      <category>비트코인</category>
      <category>자동매매</category>
      <author>aa_rong_blog</author>
      <guid isPermaLink="true">https://poalim.tistory.com/30</guid>
      <comments>https://poalim.tistory.com/30#entry30comment</comments>
      <pubDate>Mon, 26 Apr 2021 23:11:14 +0900</pubDate>
    </item>
    <item>
      <title>[비트코인] 가상화폐 자동투자 프로그램 개발하기(2) - 변동성 돌파 전략 및 백테스팅</title>
      <link>https://poalim.tistory.com/29</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이번 시간에는 가상화폐 자동투자를 실행할 '전략'에 대해 포스팅한다. 사람이 직접 하는 투자가 아니라 프로그램이 자동으로 진행하는 투자이기 때문에, 최대한 &lt;b&gt;체계적이고 일률적인 투자전략&lt;/b&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;h2 data-ke-size=&quot;size26&quot;&gt;변동성 돌파 전략&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;투자에서 사용할 수 있는 분석 기법은 크게 투자 대상의 내재적 가치(기업가치, 재무재표 등)를 분석하는 '기본적 분석(Fundamental Analysis)'과, 투자 대상의 가격 변동(차트)를 분석하는 &lt;b&gt;'기술적 분석(Technical Analysis)' &lt;/b&gt;두 가지로 나누어진다. 이 중에서 우리는 자동매매라는 특성상 기본적 분석은 애초에 불가하기 때문에 기술적 분석, 즉 차트분석에 모든 것을 맡겨야만 한다. &lt;b&gt;변동성 돌파 전략&lt;/b&gt;이란 이러한 기술적 투자의 대가로 불리는 '래리 윌리엄스(Larry R. Williams)' 가 실제로 사용한 &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;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;전일의 고가와 저가의 차이인 Range 를 구한다. (Range = 전일고가- 전일저가)&lt;/li&gt;
&lt;li&gt;당일 &quot;장중 가격 &amp;gt; 당일시가 + Range * K&quot; 를 만족하는 시점에 매수한다. (K는 0~1 사이의 값으로 가장 효율적인 값을 찾아야 한다.)&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;nbsp;&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;b&gt;백테스팅&lt;/b&gt;이라고 부른다. 백테스팅을 위하여 업비트 API 의 차트 데이터를 이용한다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;차트 불러오기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;업비트 API 에서 차트를 불러올 때는 get_ohlcv() 를 사용하면 된다. 자세한 사용법은 pyupbit github (&lt;a href=&quot;https://github.com/sharebook-kr/pyupbit&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;github.com/sharebook-kr/pyupbit&lt;/a&gt;) 에서 확인할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1619199143208&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import pyupbit
import pandas as pd
import time

coin = &quot;KRW-BTC&quot;		# 코인명
interval = &quot;day&quot;		# 차트의 종류
fees = 0.0005			# 수수료
day_count = 1300		# 데이터 수

date = None
dfs = [ ]

for i in range(day_count // 200 + 1):
    if i &amp;lt; day_count // 200 :
        df = pyupbit.get_ohlcv(coin, to = date, interval = interval)
        date = df.index[0]
    elif day_count % 200 != 0 :
        df = pyupbit.get_ohlcv(coin, to = date, interval = interval, count = day_count % 200)
    else :
        break
    dfs.append(df)
    time.sleep(0.1)

df = pd.concat(dfs).sort_index()&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;coin, interval, fees, day_count 는 각각 데이터를 불러오기 위한 변수이다. coin 은 불러올 가상화폐명을, interval 은 일봉, 주봉, 분봉 등의 구분을, fees 는 수수료, day_count 는 불러올 데이터의 수를 뜻한다.&lt;/li&gt;
&lt;li&gt;get_ohlcv() 함수에서 한 번에 불러올 수 있는 최대 데이터 수는 200 개이므로 for 반복문을 사용하여 200 개 이상의 데이터를 받아올 수 있게 하였다.&lt;/li&gt;
&lt;li&gt;업비트 API 에서 차트의 요청은 초당 10회의 제한이 있기 때문에 time.sleep(0.1) 로 0.1초씩 지연시켜주어야 오류가 발생하지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;불러온 차트는 df 에 DataFrame 형태로 저장된다. 이를 출력하여 보면 아래와 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;573&quot; data-origin-height=&quot;108&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/etux21/btq3nUCE68J/AzU8KMClEWfdyg84YOj5W0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/etux21/btq3nUCE68J/AzU8KMClEWfdyg84YOj5W0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/etux21/btq3nUCE68J/AzU8KMClEWfdyg84YOj5W0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fetux21%2Fbtq3nUCE68J%2FAzU8KMClEWfdyg84YOj5W0%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;594&quot; height=&quot;108&quot; data-origin-width=&quot;573&quot; data-origin-height=&quot;108&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;

&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;전략 구현&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 차트에서 불러온 데이터는 &lt;b&gt;open(시가), high(고가), low(저가), close(종가)&lt;/b&gt;, volume(거래량) 이다. 이 값들을 이용하여 변동성돌파전략을 실행하고 수익률을 구하여 데이터에 추가한다.&lt;/p&gt;
&lt;pre id=&quot;code_1619200602614&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;df['range'] = df['high'].shift(1) - df['low'].shift(1)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 &lt;b&gt;range&lt;/b&gt; 를 구한다. shift(1) 은 데이터를 1 만큼 아래로 당기는 명령어로 range 는 전일 고가와 전일 저가를 이용하므로 전일 데이터를 당겨서 쓰기 위함이다. 위와 같이 DataFrame 에서 column 끼리의 연산을 하면 DataFrame의 모든 데이터(row)에 적용된다.&lt;/p&gt;
&lt;pre id=&quot;code_1619200993040&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;df['targetPrice'] = df['open'] + df['range'] * K&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;targetPrice&lt;/b&gt; 는 매수목표가를 의미한다. 당일 시가 + 전일 range * K 값을 매수목표가로 정해두고 이 값을 돌파하면 매수한다.&lt;/p&gt;
&lt;pre id=&quot;code_1619201624743&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;df['drr'] = np.where(df['high'] &amp;gt; df['targetPrice'], (df['close'] / (1 + fees)) / (df['targetPrice'] * (1 + fees)) - 1, 0)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;drr&lt;/b&gt; 은 &lt;b&gt;Daily Rate of Return&lt;/b&gt; 으로 하루동안의 수익률을 나타낸다. np 는 numpy 모듈로, 사용하기 위해 &lt;b&gt;import numpy as np&lt;/b&gt; 을 포함하여야 한다. np.where 은 if 문과 유사한 역할을 한다. np.where(condition, A, B) 는 condition 이 true 이면 A 를, false 이면 B 를 반환한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;df['high] &amp;gt; df['targetPrice'] : 당일의 고가가 매수목표가보다 크므로 매수목표가에서 매수가 이루어진다.&lt;/li&gt;
&lt;li&gt;df['high] &amp;lt; df['targetPrice'] : 매수가 이루어지지 않으므로 drr 은 0 이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매수가 이루어진 경우 drr 을 계산하기 위한 식을 보면 수수료를 포함하기 때문에 다소 복잡해 보인다.&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;letter-spacing: 0px;&quot;&gt;수익률의 계산은 기본적으로 &lt;b&gt;(매도가 / 매수가) - 1&lt;/b&gt; 로 계산된다. 하지만 이는 수수료가 포함되지 않은 것으로 수수료를 포함하여서 &lt;b&gt;(실매도가 / 실매수가) - 1&lt;/b&gt; 의 계산이 이루어져야 한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;'실매도가 * (1 + 수수료) = 매도가' 이므로,&lt;b&gt; '실매도가 = 매도가 / (1 + 수수료)'&lt;/b&gt; 이다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;'매수가 * (1 + 수수료) = 실매수가' 이므로, &lt;b&gt;'실매수가 = 매수가 * (1 + 수수료)'&lt;/b&gt; 이다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&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;span style=&quot;letter-spacing: 0px;&quot;&gt;이를 토대로 &lt;b&gt;'수익률 = (매도가 / (1 + 수수료)) / (매수가 * (1+ 수수료)) - 1'&lt;/b&gt; 임을 알 수 있다. 위의 전략 설명에서 매도는 익일 시가에 한다고 하였는데, 암호화폐 시장의 특성상 장 마감시간이 존재하지 않으므로 '익일 시가 = 전일 종가' 이기 때문에 매도가로 df['close'] 를 사용하였다. (업비트상 일봉이 넘어가는 기준 시간은 am 9:00 이다.)&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;이제 하루하루의 수익률을 구했으므로, 이를 활용하여 전체 기간동안의 누적 수익률을 구한다.&lt;/p&gt;
&lt;pre id=&quot;code_1619200274094&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;df['crr'] = (df['drr'] + 1).cumprod() - 1&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;crr&lt;/b&gt; 은 &lt;b&gt;Cumulative Rate of Return&lt;/b&gt; 으로 누적 수익률을 나타낸다. 누적 수익률은 시작하는 날부터 모든 날의 수익률을 곱하여서 계산할 수 있다. cumprod() 는 where() 과 마찬가지로 numpy 모듈에 포함된 함수로 원소들의 누적 곱을 구하여주는 함수이다. drr 은 단순 수익률을 나타내기 위하여 0 ~ 1 (0% ~ 100%) 의 값을 가지도록 설계하였기 때문에 이 값들의 누적 곱은 0 으로 점점 가까워 질 수 밖에 없다. 이를 해결하기 위해 drr 값에 1 을 더한 값들의 누적 곱을 더한 후 기초값인 1을 빼줘서 마무리한다. 아래 예를 보면 이해하기 쉬울 것 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;첫 날 20%, 둘째 날 10% 의 수익률을 올린 경우 drr1 = 0.2, drr2 = 0.1 이다.&lt;/li&gt;
&lt;li&gt;이를 단순히 곱하면 0.02, 즉 2% 라는 누적 수익률이 나온다.&lt;/li&gt;
&lt;li&gt;(1 + 0.2) * (1 + 0.1) - 1 = 1.2 * 1.1 - 1 = 0.32, 즉 32% 라는 올바른 누적 수익률을 얻을 수 있다.&lt;/li&gt;
&lt;/ul&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 id=&quot;code_1619373186365&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;df['dd'] = -(((df['crr'] + 1).cummax() - (df['crr'] + 1)) / (df['crr'] + 1).cummax())
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;dd&lt;/b&gt; 는&lt;b&gt;&amp;nbsp;Draw Down&lt;/b&gt; 으로 최대 누적 수익률 대비 당일 누적 수익률의 손실률을 나타낸다. cummax() 는 마찬가지로 numpy 모듈의 함수로 누적 최대 값을 구하여 준다. 최대 누적수익률에서 당일까지의 누적 수익률을 빼서 최대 누적 수익률에서 잃은 수익률을 구한 후 이를 최대 누적 수익률로 나누어 주면 손실률이 나온다. 손실률이므로 앞에 - 를 붙여준다. 이렇게 각각의 dd 를 구하면, 그 중 최소값( = df['dd'].min() )이 최대 손실률이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지 한 후 데이터를 출력하여 보면 아래와 같이 column들이 새로 추가된 것을 볼 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;958&quot; data-origin-height=&quot;395&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cu1eVS/btq3v81y5oS/qc8Hqsoj0ZVKK6nug21cV1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cu1eVS/btq3v81y5oS/qc8Hqsoj0ZVKK6nug21cV1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cu1eVS/btq3v81y5oS/qc8Hqsoj0ZVKK6nug21cV1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcu1eVS%2Fbtq3v81y5oS%2Fqc8Hqsoj0ZVKK6nug21cV1%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;958&quot; height=&quot;395&quot; data-origin-width=&quot;958&quot; data-origin-height=&quot;395&quot;/&gt;&lt;/span&gt;&lt;/figure&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;이렇게 터미널상으로 확인하는 것은 눈에 잘 들어오지 않는다. 이 데이터들을 엑셀파일로 저장하여 보도록 하자. 파이썬에서 엑셀파일을 다루기 위한 모듈로는 openpyxl 이 있다. 일단&amp;nbsp;&lt;b&gt;pip install openpyxl&lt;/b&gt; 명령어로 모듈을 설치하여 준다. 그 후 DataFrame 을 엑셀 파일로 저장하는 것은 to_excel(file_name) 명령어 하나만 있으면 된다. 프로그램의 맨 아래에 이 코드를 추가하여 준다.&lt;/p&gt;
&lt;pre id=&quot;code_1619242212467&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;df.to_excel(&quot;crypto_history.xlsx&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 프로그램을 실행시켜 보면 crypto_hitory.xlsx 라는 엑셀 파일이 같은 폴더에 생성되었을 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;962&quot; data-origin-height=&quot;796&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/W1lwL/btq3vV2h6RN/FeoEpwEL6uoVyfocmfd430/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/W1lwL/btq3vV2h6RN/FeoEpwEL6uoVyfocmfd430/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/W1lwL/btq3vV2h6RN/FeoEpwEL6uoVyfocmfd430/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FW1lwL%2Fbtq3vV2h6RN%2FFeoEpwEL6uoVyfocmfd430%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;962&quot; height=&quot;796&quot; data-origin-width=&quot;962&quot; data-origin-height=&quot;796&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 모양을 하고 있다. 파일의 맨 아래까지 내렸을 때, 마지막 데이터의 crr 에서 모든 기간의 누적수익률을 볼 수 있다. drr, crr, dd 는 아무래도 % 로 나타나 있는 것이 보기 편할 것 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;482&quot; data-origin-height=&quot;407&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uc7X1/btq3qgsOPJc/k43VazFpk6TsxHiylIoWt1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uc7X1/btq3qgsOPJc/k43VazFpk6TsxHiylIoWt1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uc7X1/btq3qgsOPJc/k43VazFpk6TsxHiylIoWt1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fuc7X1%2Fbtq3qgsOPJc%2Fk43VazFpk6TsxHiylIoWt1%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;370&quot; height=&quot;407&quot; data-origin-width=&quot;482&quot; data-origin-height=&quot;407&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;widthContent&quot; data-origin-width=&quot;541&quot; data-origin-height=&quot;549&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cF8JSA/btq3l5sc8ZS/zdaGDKjYjpcWua3NAo8ORK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cF8JSA/btq3l5sc8ZS/zdaGDKjYjpcWua3NAo8ORK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cF8JSA/btq3l5sc8ZS/zdaGDKjYjpcWua3NAo8ORK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcF8JSA%2Fbtq3l5sc8ZS%2FzdaGDKjYjpcWua3NAo8ORK%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;407&quot; height=&quot;549&quot; data-origin-width=&quot;541&quot; data-origin-height=&quot;549&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;표시 형식에서 백분율을 선택하고 소수 자리수를 2로 설정한 후 확인을 누르면 데이터가 백분율로 표시된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;365&quot; data-origin-height=&quot;771&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Nrq1s/btq3nS0gCei/kPtmeAb4KJQtkMpeXxWoeK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Nrq1s/btq3nS0gCei/kPtmeAb4KJQtkMpeXxWoeK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Nrq1s/btq3nS0gCei/kPtmeAb4KJQtkMpeXxWoeK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNrq1s%2Fbtq3nS0gCei%2FkPtmeAb4KJQtkMpeXxWoeK%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;365&quot; height=&quot;771&quot; data-origin-width=&quot;365&quot; data-origin-height=&quot;771&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;b&gt;비트코인(KRW-BTC), 이더리움(KRW-ETH), 리플(KRW-XRP)&lt;/b&gt; 세 가지 코인으로 진행하였고, 수수료는 현재 업비트 수수료 기준인 0.05%, 데이터 갯수는 업비트 API 가 최대로 가지고 있는 데이터 수가 1300 정도인 이유로 1300 일간의 데이터로 진행하였다.&lt;/p&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;비트코인(KRW-BTC)&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 200px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 13.4884%; height: 20px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 26.1628%; height: 20px; text-align: center;&quot;&gt;기간 누적 수익률&lt;/td&gt;
&lt;td style=&quot;width: 27.7906%; height: 20px; text-align: center;&quot;&gt;최대 손실률&lt;/td&gt;
&lt;td style=&quot;width: 32.5582%; height: 20px; text-align: center;&quot;&gt;알고리즘 적용 없이 보유 수익률&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 13.4884%; height: 20px; text-align: center;&quot;&gt;K = 0.1&lt;/td&gt;
&lt;td style=&quot;width: 26.1628%; height: 20px; text-align: center;&quot;&gt;430.92%&lt;/td&gt;
&lt;td style=&quot;width: 27.7906%; height: 20px; text-align: center;&quot;&gt;-80.03&lt;span style=&quot;color: #333333;&quot;&gt;%&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 32.5582%; height: 20px; text-align: center;&quot;&gt;1097.44%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 13.4884%; height: 20px; text-align: center;&quot;&gt;K = 0.2&lt;/td&gt;
&lt;td style=&quot;width: 26.1628%; height: 20px; text-align: center;&quot;&gt;563.27&lt;span style=&quot;color: #333333;&quot;&gt;%&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 27.7906%; height: 20px; text-align: center;&quot;&gt;-67.23&lt;span style=&quot;color: #333333;&quot;&gt;%&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 32.5582%; height: 20px; text-align: center;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;1097.44%&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 13.4884%; height: 20px; text-align: center;&quot;&gt;K = 0.3&lt;/td&gt;
&lt;td style=&quot;width: 26.1628%; height: 20px; text-align: center;&quot;&gt;1229.17&lt;span style=&quot;color: #333333;&quot;&gt;%&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 27.7906%; height: 20px; text-align: center;&quot;&gt;-48.28&lt;span style=&quot;color: #333333;&quot;&gt;%&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 32.5582%; height: 20px; text-align: center;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;1097.44%&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 13.4884%; height: 20px; text-align: center;&quot;&gt;K = 0.4&lt;/td&gt;
&lt;td style=&quot;width: 26.1628%; height: 20px; text-align: center;&quot;&gt;2005.12&lt;span style=&quot;color: #333333;&quot;&gt;%&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 27.7906%; height: 20px; text-align: center;&quot;&gt;-25.47&lt;span style=&quot;color: #333333;&quot;&gt;%&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 32.5582%; height: 20px; text-align: center;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;1097.44%&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 13.4884%; height: 20px; text-align: center;&quot;&gt;K = 0.5&lt;/td&gt;
&lt;td style=&quot;width: 26.1628%; height: 20px; text-align: center;&quot;&gt;2010.45&lt;span style=&quot;color: #333333;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 27.7906%; height: 20px; text-align: center;&quot;&gt;-20.24&lt;span style=&quot;color: #333333;&quot;&gt;%&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 32.5582%; height: 20px; text-align: center;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;1097.44%&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 13.4884%; height: 20px; text-align: center;&quot;&gt;K = 0.6&lt;/td&gt;
&lt;td style=&quot;width: 26.1628%; height: 20px; text-align: center;&quot;&gt;1256.80&lt;span style=&quot;color: #333333;&quot;&gt;%&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 27.7906%; height: 20px; text-align: center;&quot;&gt;-20.81&lt;span style=&quot;color: #333333;&quot;&gt;%&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 32.5582%; height: 20px; text-align: center;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;1097.44%&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 13.4884%; height: 20px; text-align: center;&quot;&gt;K = 0.7&lt;/td&gt;
&lt;td style=&quot;width: 26.1628%; height: 20px; text-align: center;&quot;&gt;1010.61&lt;span style=&quot;color: #333333;&quot;&gt;%&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 27.7906%; height: 20px; text-align: center;&quot;&gt;-23.01&lt;span style=&quot;color: #333333;&quot;&gt;%&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 32.5582%; height: 20px; text-align: center;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;1097.44%&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 13.4884%; height: 20px; text-align: center;&quot;&gt;K = 0.8&lt;/td&gt;
&lt;td style=&quot;width: 26.1628%; height: 20px; text-align: center;&quot;&gt;749.33&lt;span style=&quot;color: #333333;&quot;&gt;%&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 27.7906%; height: 20px; text-align: center;&quot;&gt;-20.62&lt;span style=&quot;color: #333333;&quot;&gt;%&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 32.5582%; height: 20px; text-align: center;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;1097.44%&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 13.4884%; height: 20px; text-align: center;&quot;&gt;K = 0.9&lt;/td&gt;
&lt;td style=&quot;width: 26.1628%; height: 20px; text-align: center;&quot;&gt;572.70&lt;span style=&quot;color: #333333;&quot;&gt;%&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 27.7906%; height: 20px; text-align: center;&quot;&gt;-21.98&lt;span style=&quot;color: #333333;&quot;&gt;%&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 32.5582%; height: 20px; text-align: center;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;1097.44%&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시작일인 2017-10-13 의 시가에 비트코인을 매수하여 종료일인 2021-04-24 종가에 매도하였다고 할 때 수익률은 1097.44% 였다. 여기에 변동성 돌파 전략을 적용하면, K 의 값에 따라 최대 2000% 에 달하는 수익률을 얻을 수 있었다. K 의 값이 작을수록 작은 상승세에도 금방 돌파신호로 인식하기 때문에 최대 손실률이 커지는 경향이 있지만, K = 0.4 이상의 값에서는 유의미한 차이가 없음을 볼 수 있다. K 의 값이 일정 수준 이상에서는 커질수록 시가와 매수목표가의 차이가 커져서 그 상승분만큼의 이익을 보지 못하므로 누적 수익률이 떨어진다. K 값에 따른 이러한 성향으로 인하여 변동성 돌파 전략의 사용자들은 K 값을 주로 0.5 로 사용한다. 실제로 이번 백테스팅 결과에서도 &lt;b&gt;K = 0.5 일때 가장 높은 수익률&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;span style=&quot;color: #333333;&quot;&gt;이더리움(KRW-ETH)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 200px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 13.4884%; text-align: center; height: 20px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 26.1628%; text-align: center; height: 20px;&quot;&gt;기간 누적 수익률&lt;/td&gt;
&lt;td style=&quot;width: 27.7906%; text-align: center; height: 20px;&quot;&gt;최대 손실률&lt;/td&gt;
&lt;td style=&quot;width: 32.5582%; text-align: center; height: 20px;&quot;&gt;알고리즘 적용 없이 보유 수익률&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 13.4884%; text-align: center; height: 20px;&quot;&gt;K = 0.1&lt;/td&gt;
&lt;td style=&quot;width: 26.1628%; text-align: center; height: 20px;&quot;&gt;48.63%&lt;/td&gt;
&lt;td style=&quot;width: 27.7906%; text-align: center; height: 20px;&quot;&gt;-96.25&lt;span style=&quot;color: #333333;&quot;&gt;%&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 32.5582%; text-align: center; height: 20px;&quot;&gt;744.34%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 13.4884%; text-align: center; height: 20px;&quot;&gt;K = 0.2&lt;/td&gt;
&lt;td style=&quot;width: 26.1628%; text-align: center; height: 20px;&quot;&gt;237.38&lt;span style=&quot;color: #333333;&quot;&gt;%&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 27.7906%; text-align: center; height: 20px;&quot;&gt;-92.92&lt;span style=&quot;color: #333333;&quot;&gt;%&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 32.5582%; text-align: center; height: 20px;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;744.34%&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 13.4884%; text-align: center; height: 20px;&quot;&gt;K = 0.3&lt;/td&gt;
&lt;td style=&quot;width: 26.1628%; text-align: center; height: 20px;&quot;&gt;1724.84&lt;span style=&quot;color: #333333;&quot;&gt;%&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 27.7906%; text-align: center; height: 20px;&quot;&gt;-42.07&lt;span style=&quot;color: #333333;&quot;&gt;%&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 32.5582%; text-align: center; height: 20px;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;744.34%&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 13.4884%; text-align: center; height: 20px;&quot;&gt;K = 0.4&lt;/td&gt;
&lt;td style=&quot;width: 26.1628%; text-align: center; height: 20px;&quot;&gt;1954.64&lt;span style=&quot;color: #333333;&quot;&gt;%&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 27.7906%; text-align: center; height: 20px;&quot;&gt;-33.94&lt;span style=&quot;color: #333333;&quot;&gt;%&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 32.5582%; text-align: center; height: 20px;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;744.34%&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 13.4884%; text-align: center; height: 20px;&quot;&gt;K = 0.5&lt;/td&gt;
&lt;td style=&quot;width: 26.1628%; text-align: center; height: 20px;&quot;&gt;1152.77&lt;span style=&quot;color: #333333;&quot;&gt;%&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 27.7906%; text-align: center; height: 20px;&quot;&gt;-34.53&lt;span style=&quot;color: #333333;&quot;&gt;%&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 32.5582%; text-align: center; height: 20px;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;744.34%&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 13.4884%; text-align: center; height: 20px;&quot;&gt;K = 0.6&lt;/td&gt;
&lt;td style=&quot;width: 26.1628%; text-align: center; height: 20px;&quot;&gt;1978.92&lt;span style=&quot;color: #333333;&quot;&gt;%&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 27.7906%; text-align: center; height: 20px;&quot;&gt;-25.64&lt;span style=&quot;color: #333333;&quot;&gt;%&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 32.5582%; text-align: center; height: 20px;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;744.34%&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 13.4884%; text-align: center; height: 20px;&quot;&gt;K = 0.7&lt;/td&gt;
&lt;td style=&quot;width: 26.1628%; text-align: center; height: 20px;&quot;&gt;1351.71&lt;span style=&quot;color: #333333;&quot;&gt;%&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 27.7906%; text-align: center; height: 20px;&quot;&gt;-35.32&lt;span style=&quot;color: #333333;&quot;&gt;%&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 32.5582%; text-align: center; height: 20px;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;744.34%&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 13.4884%; text-align: center; height: 20px;&quot;&gt;K = 0.8&lt;/td&gt;
&lt;td style=&quot;width: 26.1628%; text-align: center; height: 20px;&quot;&gt;1175.63&lt;span style=&quot;color: #333333;&quot;&gt;%&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 27.7906%; text-align: center; height: 20px;&quot;&gt;-27.55&lt;span style=&quot;color: #333333;&quot;&gt;%&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 32.5582%; text-align: center; height: 20px;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;744.34%&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 13.4884%; text-align: center; height: 20px;&quot;&gt;K = 0.9&lt;/td&gt;
&lt;td style=&quot;width: 26.1628%; text-align: center; height: 20px;&quot;&gt;932.97&lt;span style=&quot;color: #333333;&quot;&gt;%&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 27.7906%; text-align: center; height: 20px;&quot;&gt;-31.96&lt;span style=&quot;color: #333333;&quot;&gt;%&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 32.5582%; text-align: center; height: 20px;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;744.34%&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;비트코인과 다르게 이더리움에서는 &lt;b&gt;K = 0.6 일 때 가장 높은 수익률&lt;/b&gt;을 보인다.&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;&lt;b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;리플(KRW-XRP)&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 13.4884%; text-align: center;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 26.1628%; text-align: center;&quot;&gt;기간 누적 수익률&lt;/td&gt;
&lt;td style=&quot;width: 27.7906%; text-align: center;&quot;&gt;최대 손실률&lt;/td&gt;
&lt;td style=&quot;width: 32.5582%; text-align: center;&quot;&gt;알고리즘 적용 없이 보유 수익률&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 13.4884%; text-align: center;&quot;&gt;K = 0.1&lt;/td&gt;
&lt;td style=&quot;width: 26.1628%; text-align: center;&quot;&gt;85.65%&lt;/td&gt;
&lt;td style=&quot;width: 27.7906%; text-align: center;&quot;&gt;-93.17&lt;/td&gt;
&lt;td style=&quot;width: 32.5582%; text-align: center;&quot;&gt;522.10%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 13.4884%; text-align: center;&quot;&gt;K = 0.2&lt;/td&gt;
&lt;td style=&quot;width: 26.1628%; text-align: center;&quot;&gt;503.18&lt;span style=&quot;color: #333333;&quot;&gt;%&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 27.7906%; text-align: center;&quot;&gt;-76.58&lt;span style=&quot;color: #333333;&quot;&gt;%&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 32.5582%; text-align: center;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;522.10%&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 13.4884%; text-align: center;&quot;&gt;K = 0.3&lt;/td&gt;
&lt;td style=&quot;width: 26.1628%; text-align: center;&quot;&gt;958.38&lt;span style=&quot;color: #333333;&quot;&gt;%&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 27.7906%; text-align: center;&quot;&gt;-51.64&lt;span style=&quot;color: #333333;&quot;&gt;%&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 32.5582%; text-align: center;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;522.10%&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 13.4884%; text-align: center;&quot;&gt;K = 0.4&lt;/td&gt;
&lt;td style=&quot;width: 26.1628%; text-align: center;&quot;&gt;2046.60&lt;span style=&quot;color: #333333;&quot;&gt;%&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 27.7906%; text-align: center;&quot;&gt;-48.52&lt;span style=&quot;color: #333333;&quot;&gt;%&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 32.5582%; text-align: center;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;522.10%&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 13.4884%; text-align: center;&quot;&gt;K = 0.5&lt;/td&gt;
&lt;td style=&quot;width: 26.1628%; text-align: center;&quot;&gt;4412.22&lt;span style=&quot;color: #333333;&quot;&gt;%&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 27.7906%; text-align: center;&quot;&gt;-35.62&lt;span style=&quot;color: #333333;&quot;&gt;%&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 32.5582%; text-align: center;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;522.10%&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 13.4884%; text-align: center;&quot;&gt;K = 0.6&lt;/td&gt;
&lt;td style=&quot;width: 26.1628%; text-align: center;&quot;&gt;4412.22&lt;span style=&quot;color: #333333;&quot;&gt;%&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 27.7906%; text-align: center;&quot;&gt;-35.62&lt;span style=&quot;color: #333333;&quot;&gt;%&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 32.5582%; text-align: center;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;522.10%&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 13.4884%; text-align: center;&quot;&gt;K = 0.7&lt;/td&gt;
&lt;td style=&quot;width: 26.1628%; text-align: center;&quot;&gt;2485.53&lt;span style=&quot;color: #333333;&quot;&gt;%&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 27.7906%; text-align: center;&quot;&gt;-37.91&lt;span style=&quot;color: #333333;&quot;&gt;%&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 32.5582%; text-align: center;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;522.10%&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 13.4884%; text-align: center;&quot;&gt;K = 0.8&lt;/td&gt;
&lt;td style=&quot;width: 26.1628%; text-align: center;&quot;&gt;1494.27&lt;span style=&quot;color: #333333;&quot;&gt;%&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 27.7906%; text-align: center;&quot;&gt;-37.36&lt;span style=&quot;color: #333333;&quot;&gt;%&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 32.5582%; text-align: center;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;522.10%&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 13.4884%; text-align: center;&quot;&gt;K = 0.9&lt;/td&gt;
&lt;td style=&quot;width: 26.1628%; text-align: center;&quot;&gt;546.61&lt;span style=&quot;color: #333333;&quot;&gt;%&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 27.7906%; text-align: center;&quot;&gt;-38.86&lt;span style=&quot;color: #333333;&quot;&gt;%&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 32.5582%; text-align: center;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;522.10%&lt;/span&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;b&gt;K = 0.5, 0.6 일 때 가장 높은 수익률&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;최대 손실률(MDD) 이 낮다&lt;/b&gt;는 것이다. 가상화폐는 기본적으로 변동폭이 커서 같은 기간 동안 최대 손실률은 -90% 수준에 달한다. 이는 기간 중 나의 자산이 1/10 이 되는 기간이 있다는 것을 의미하고 일반적인 투자자가 이러한 상황을 버티기란 쉽지 않다. 변동성 돌파 전략은 MDD 를 -20% ~ -30% 수준으로 낮추어 주어서 투자를 유지하기 쉽게 만들고 전략의 신뢰도를 더한다.&lt;/p&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://github.com/poArlim/crypto-auto&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;github.com/poArlim/crypto-auto&lt;/a&gt; 에 backtesting.py 라는 이름으로 올려놓았다.&lt;/p&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>
      <category>변동성 돌파 전략</category>
      <category>비트코인</category>
      <category>암호화폐</category>
      <category>업비트</category>
      <category>이더리움</category>
      <category>자동매매</category>
      <category>자동투자</category>
      <category>파이썬</category>
      <author>aa_rong_blog</author>
      <guid isPermaLink="true">https://poalim.tistory.com/29</guid>
      <comments>https://poalim.tistory.com/29#entry29comment</comments>
      <pubDate>Sat, 24 Apr 2021 03:15:04 +0900</pubDate>
    </item>
    <item>
      <title>[Pintos] Project 1 : Thread(스레드) - Alarm Clock</title>
      <link>https://poalim.tistory.com/28</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스트에서는 핀토스의 첫 번째 과제인 Alarm Clock 을 구현해보도록 한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Alarm Clock&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;운영체제에는 실행중인 스레드를 잠시 재웠다가 일정 시간이 지나면 다시 깨우도록 하는 기능이 있는데, 이 기능을 &lt;b&gt;Alarm Clock&lt;/b&gt; 이라고 한다. 현재 핀토스에 구현되어 있는 Alarm Clock 기능은 busy-waiting 방식으로 구현되어 있는데 이는 매우 비효율적으로 많은 CPU 시간을 낭비하게 한다. busy-waiting 방식에서 sleep 명령을 받은 스레드의 진행 흐름은 아래와 같이 진행된다.&lt;/p&gt;
&lt;pre id=&quot;code_1618888522014&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;잠이듬 -&amp;gt; 깨어남 -&amp;gt; 시간확인 -&amp;gt; 다시잠 -&amp;gt; 깨어남 -&amp;gt; 시간확인 -&amp;gt; ... -&amp;gt; 깨어남 -&amp;gt; 시간확인(일어날시간) -&amp;gt; 깨어남&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 문제가 발생하는 내면을 살펴보면 &lt;b&gt;스레드의 상태가 running state 와 ready state 를 반복&lt;/b&gt;함을 볼 수 있다. running state 에서 sleep 명령을 받은 스레드는 ready state 가 되어 ready queue 에 추가되고 ready queue 에 있는 스레드들은 자신의 차례가 되면 일어날 시간이 되었는지에 상관없이 깨워져 running state 가 된다. 이렇게 running state 가 된 스레드는 자신이 일어날 시간이 되었는지 확인하고 아직 일어날 시간이 안 됐다면 다시 ready state 로 전환한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-filename=&quot;스크린샷 2021-04-20 오후 12.24.08.png&quot; data-origin-width=&quot;576&quot; data-origin-height=&quot;458&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/boocgH/btq21CiyjBi/Jw84p7KG1kENYzqN3OooaK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/boocgH/btq21CiyjBi/Jw84p7KG1kENYzqN3OooaK/img.png&quot; data-alt=&quot;busy-waiting 에서 thread 의 상태&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/boocgH/btq21CiyjBi/Jw84p7KG1kENYzqN3OooaK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FboocgH%2Fbtq21CiyjBi%2FJw84p7KG1kENYzqN3OooaK%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;265&quot; height=&quot;458&quot; data-filename=&quot;스크린샷 2021-04-20 오후 12.24.08.png&quot; data-origin-width=&quot;576&quot; data-origin-height=&quot;458&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;busy-waiting 에서 thread 의 상태&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;스레드를 ready state 가 아닌&lt;/b&gt; &lt;b&gt;block state 로 &lt;/b&gt;두어서 깨어날 시간이 되기 전까지는 스케줄링에 포함되지 않도록 하고, &lt;b&gt;깨어날 시간이 되었을 때 ready state 로&lt;/b&gt; 바꾸어 주면 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-filename=&quot;스크린샷 2021-04-20 오후 12.29.00.png&quot; data-origin-width=&quot;556&quot; data-origin-height=&quot;374&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c15mtH/btq2XaUPmBr/Bz8EgkGS0AamHjUj9iFj5k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c15mtH/btq2XaUPmBr/Bz8EgkGS0AamHjUj9iFj5k/img.png&quot; data-alt=&quot;개선된 thread 상태변화&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c15mtH/btq2XaUPmBr/Bz8EgkGS0AamHjUj9iFj5k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc15mtH%2Fbtq2XaUPmBr%2FBz8EgkGS0AamHjUj9iFj5k%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;247&quot; height=&quot;374&quot; data-filename=&quot;스크린샷 2021-04-20 오후 12.29.00.png&quot; data-origin-width=&quot;556&quot; data-origin-height=&quot;374&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;개선된 thread 상태변화&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Busy-waiting 코드&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 핀토스에 구현되어 있는 busy-waiting 을 이용한 timer_sleep() 코드는 아래와 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1618889548678&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* devices/timer.c */
void timer_sleep (int64_t ticks) {
  int64_t start = timer_ticks ();
  while (timer_elapsed (start) &amp;lt; ticks) 
    thread_yield ();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ticks 란 핀토스 내부에서 시간을 나타내기 위한 값으로 부팅 이후에 일정한 시간마다 1 씩 증가한다.&lt;/p&gt;
&lt;pre id=&quot;code_1618889845976&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* devices/timer.h */
/* Number of timer interrupts per second. */
#define TIMER_FREQ 100&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1 tick 의 시간을 설정하여 줄 수 있는데, 위 코드에서 볼 수 있듯이 현재 핀토스의 1 tick 은 1ms 로 설정되어 있다. 이에 따라 운영체제는 1ms 마다 timer 인터럽트를 실행시키고 ticks 값을 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;timer_sleep() 함수를 분석하여 보자. &lt;b&gt;timer_ticks()&lt;/b&gt; &lt;b&gt;함수는 현재 ticks 값을 반환&lt;/b&gt;하는 함수로 start 에 현재 시간(ticks)을 저장한다. timer_elapsed() 함수는 특정시간 이후로 경과된 시간(ticks) 를 반환한다. 즉, &lt;b&gt;timer_elapsed(start) 는 start 이후로 경과된 시간(ticks)을 반환한다.&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1618890296149&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* devices/timer.c */
int64_t timer_elapsed (int64_t then) {
  return timer_ticks () - then;
}&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;이에 따라, ready list 에서 자신의 차례가 된 스레드는 while 문의 조건에 의해 start 이후 경과된 시간이 ticks 보다 커질때까지 thread_yield () 를 호출하여 ready list 의 맨 뒤로 이동하기를 반복한다.&lt;/p&gt;

&lt;h2 data-ke-size=&quot;size26&quot;&gt;Sleep / Awake 구현&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Alarm clock 을 개선하는 기본적인 아이디어는 위에서 언급한대로 잠이 들 때 ready state 가 아니라 block state 로 보내고 깨어날 시간이 되면 깨워서 ready state 로 보내는 것이다. 이를 구현해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선, block state 에서는 스레드가 일어날 시간이 되었는지 주기적으로 확인하지 않기 때문에 스레드마다 &lt;b&gt;일어나야 하는 시간에 대한 정보&lt;/b&gt;를 저장하고 있어야 한다. &lt;b&gt;wakeup&lt;/b&gt; &lt;b&gt;이라는 변수를 만들어 thread 구조체에 추가&lt;/b&gt;하겠다.&lt;/p&gt;
&lt;pre id=&quot;code_1618892200134&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* thread/thread.h */
struct thread{
    ...
    int64_t wakeup; // 깨어나야 하는 ticks 값
    ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 핀토스에서 스레드들을 관리하는 리스트는 ready list 와 all list 두 개만 존재한다. 잠이 들어 block 상태가 된 스레드들은 all list 에 존재하지만, sleep 상태의 스레드들로만 이루어진 리스트를 만들어 관리하면 훨씬 편해진다. &lt;b&gt;sleep_list 를 추가하고 초기화&lt;/b&gt;까지 시켜준다.&lt;/p&gt;
&lt;pre id=&quot;code_1618892754863&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* thread/thread.c */
static struct list sleep_list;


void
thread_init (void) 
{
  ...
  list_init (&amp;amp;ready_list);
  list_init (&amp;amp;all_list);
  list_init (&amp;amp;sleep_list);
  ...
}&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;&amp;nbsp;&lt;b&gt;재워야 할 스레드를 sleep_list 에 추가하고, 스레드 상태를 block state 로&lt;/b&gt; 만들어주는 것으로 충분할 것 같다. 한 가지 주의할 점은 CPU 가 항상 실행 상태를 유지하게 하기 위해 &lt;b&gt;idle 스레드는 sleep 되지 않아야&lt;/b&gt; 한다는 것이다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1618893068061&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* thread/thread.c */
void
thread_sleep (int64_t ticks)
{
  struct thread *cur;
  enum intr_level old_level;

  old_level = intr_disable ();	// 인터럽트 off
  cur = thread_current ();
  
  ASSERT (cur != idle_thread);

  cur-&amp;gt;wakeup = ticks;			// 일어날 시간을 저장
  list_push_back (&amp;amp;sleep_list, &amp;amp;cur-&amp;gt;elem);	// sleep_list 에 추가
  thread_block ();				// block 상태로 변경

  intr_set_level (old_level);	// 인터럽트 on
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스레드를 재우는 함수를 만들었으니 timer_sleep() 함수의 while 문을 새로운 함수로 바꾸어 주자.&lt;/p&gt;
&lt;pre id=&quot;code_1618893393585&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* devices/timer.c */
void 
timer_sleep (int64_t ticks) 
{
  int64_t start = timer_ticks ();
  thread_sleep (start + ticks);
}&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;이제 timer_sleep() 함수가 호출되면 스레드가 block 상태로 들어가게 되었다. 이렇게 block 된 스레드들은 일어날 시간이 되었을 때 awake 되어야 한다. &lt;b&gt;sleep_list 를 돌면서 일어날 시간이 지난 스레드들을 찾아서 ready list 로 옮겨주고, 스레드 상태를 ready state 로&lt;/b&gt; 변경시켜주도록 하자.&lt;/p&gt;
&lt;pre id=&quot;code_1618893937395&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* thread/thread.c */
void
thread_awake (int64_t ticks)
{
  struct list_elem *e = list_begin (&amp;amp;sleep_list);

  while (e != list_end (&amp;amp;sleep_list)){
    struct thread *t = list_entry (e, struct thread, elem);
    if (t-&amp;gt;wakeup &amp;lt;= ticks){	// 스레드가 일어날 시간이 되었는지 확인
      e = list_remove (e);	// sleep list 에서 제거
      thread_unblock (t);	// 스레드 unblock
    }
    else 
      e = list_next (e);
  }
}&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;※ 새롭게 sleep, awake 함수를 추가하였으므로 thread.h 에 프로토타입을 선언해주어야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1618894423617&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* thread/thread.h */
...
void thread_sleep(int64_t ticks);
void thread_awake(int64_t ticks);
...&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;위에서 1 tick 이 경과할 때마다 timer 인터럽트가 실행된다고 하였다. 이 &lt;b&gt;timer interrupt 작업에 awake 작업을 포함&lt;/b&gt;시키면 ticks 가 증가할때마다 깨워야 할 스레드가 있는지 찾고, 깨워줄 수 있다. timer_interrupt handler 함수는 devices/timer.c 에 구현되어 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1618894273600&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* devices/timer.c */
static void
timer_interrupt (struct intr_frame *args UNUSED)
{
  ticks++;
  thread_tick ();
  thread_awake (ticks);	// ticks 가 증가할때마다 awake 작업 수행
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결과&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pintos/src/thread 폴더에서 &lt;b&gt;make&lt;/b&gt; 명령으로 컴파일을 하고, &lt;b&gt;pintos -q run alarm-multiple&lt;/b&gt; 명령을 실행하면 아래와 같은 결과를 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;1460&quot; data-origin-height=&quot;1692&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/biiO7m/btq2XkchXfu/0f4Oos7kcjnFk3mUrTsFFK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/biiO7m/btq2XkchXfu/0f4Oos7kcjnFk3mUrTsFFK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/biiO7m/btq2XkchXfu/0f4Oos7kcjnFk3mUrTsFFK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbiiO7m%2Fbtq2XkchXfu%2F0f4Oos7kcjnFk3mUrTsFFK%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;553&quot; height=&quot;1692&quot; data-origin-width=&quot;1460&quot; data-origin-height=&quot;1692&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에 구현되어 있던 busy-waiting 방식으로 실행 시 &lt;b&gt;Thread: 0 idle ticks&amp;nbsp;&lt;/b&gt;로 나오던 부분이 &lt;b&gt;550 idle ticks&amp;nbsp;&lt;/b&gt;로 바뀌었다. 이것은 idle 스레드가 550 ticks 동안 실행되었다는 것이고, &lt;b&gt;idle 스레드는 다른 어떤 스레드도 실행되지 않는 상태일 때 실행되는 스레드&lt;/b&gt;로 550 ticks 동안의 쉬는 시간이 발생했다는 뜻과 같습니다. busy-waiting 방식일 때 ready list 가 비어있는 순간이 없기 때문에 idle 스레드가 실행될 일이 없어 0 idle ticks 로 출력된 것과 달리 &lt;b&gt;sleep/awake 방식에서 시스템 자원의 낭비가 줄어든 것&lt;/b&gt;을 볼 수 있다.&lt;/p&gt;</description>
      <category>프로젝트/Pintos</category>
      <category>alarm clock</category>
      <category>os</category>
      <category>pintos</category>
      <category>Thread</category>
      <category>운영체제</category>
      <author>aa_rong_blog</author>
      <guid isPermaLink="true">https://poalim.tistory.com/28</guid>
      <comments>https://poalim.tistory.com/28#entry28comment</comments>
      <pubDate>Mon, 19 Apr 2021 15:12:05 +0900</pubDate>
    </item>
    <item>
      <title>[비트코인] 가상화폐 자동투자 프로그램 개발하기(1) - 업비트 API</title>
      <link>https://poalim.tistory.com/27</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;작년 코로나19 가 세계를 덮친지 1년만에 KOSPI 지수가 3000 을 넘어서면서 일어난 재테크 열풍으로 요즘 남녀노소 누구나 주식 및 가상화폐 투자에 뛰어들고 있다. 이러한 상황 속에서 주변 사람들, 심지어 대학교 새내기들조차 투자에 빠져 밤을 새고 수업을 가지 못하는 상황을 주변에서 종종 보면서 이들이 직접 밤새 투자에 참여하지 않아도 &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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;주식 vs 가상화폐&lt;/b&gt;&lt;/h4&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;365일 24시간&lt;/b&gt; 쉬지 않고 운영된다. 이는 직접 투자에 시간을 소비하지 않아도 되는 자동매매의 장점을 극대화 시킬 수 있다.&lt;/li&gt;
&lt;li&gt;주식은 상장 기업의 수가 너무 많아서 종목 선정에 따라 수익률이 천차만별로 달라질 수 있다. 반면에 가상화폐는 모든 코인시장을 대표하는 '&lt;b&gt;비트코인&lt;/b&gt;' 이라는 가상화폐가 존재하기 때문에 이 '&lt;b&gt;비트코인&lt;/b&gt;'을 대상으로 자동매매를 진행한다.&lt;/li&gt;
&lt;li&gt;현시점에서 일반적으로 주식 시장보다 가상화폐 시장의 &lt;b&gt;변동성&lt;/b&gt;이 크다. 기본적으로 '단기 트레이딩' 방식을 따르는 자동매매 특성상 변동성이 클수록 수익률의 극대화가 가능하다.&lt;/li&gt;
&lt;li&gt;가상화폐 거래소의 &lt;b&gt;API 사용&lt;/b&gt;이 주식 시장의 API 보다 난이도와 용이성 측면에서 더 좋다고 판단하였다.&lt;/li&gt;
&lt;li&gt;가장 중요한 이유로, 자동매매 특성상 잦은 거래가 따르는데 주식 시장은 매 거래마다 (&lt;b&gt;거래세 0.15%&lt;/b&gt; + 수수료 0.015% = 0.165%, 2021년 키움증권 기준) 씩 차감되지만 가상화폐 거래의 경우 2021 년까지는 세금이 없기 때문에 (수수료 0.05%, 2021년 업비트 기준) 만 차감된다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;업비트 API 사용하기&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Open API Key 발급&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발은 국내 가상화폐 거래소인 '업비트' 에서 제공하는 API 를 사용하여 진행한다. 업비트 가입 및 계좌 연결은 이미 되어 있다고 생각하고, Open API 키를 발급받기 위해 &lt;b&gt;'업비트 로그인' -&amp;gt; '고객센터' -&amp;gt; 'Open API 안내' -&amp;gt; 'Open API 사용하기'&lt;/b&gt; 순으로 진행한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-filename=&quot;스크린샷 2021-04-17 오후 8.16.01.png&quot; data-origin-width=&quot;2880&quot; data-origin-height=&quot;1584&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpmJV8/btq2My9fOkT/lS1Hwl8wJbwrjcdpZvkj91/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpmJV8/btq2My9fOkT/lS1Hwl8wJbwrjcdpZvkj91/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpmJV8/btq2My9fOkT/lS1Hwl8wJbwrjcdpZvkj91/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbpmJV8%2Fbtq2My9fOkT%2FlS1Hwl8wJbwrjcdpZvkj91%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;2880&quot; height=&quot;1584&quot; data-filename=&quot;스크린샷 2021-04-17 오후 8.16.01.png&quot; data-origin-width=&quot;2880&quot; data-origin-height=&quot;1584&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Open API 사용하기를 클릭하여 들어오면 아래와 같은 창이 나온다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-filename=&quot;스크린샷 2021-04-17 오후 8.19.43.png&quot; data-origin-width=&quot;2880&quot; data-origin-height=&quot;1550&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2Iorc/btq2MAMIA68/S7oLMbJE66KqmECvRWkWu1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2Iorc/btq2MAMIA68/S7oLMbJE66KqmECvRWkWu1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2Iorc/btq2MAMIA68/S7oLMbJE66KqmECvRWkWu1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2Iorc%2Fbtq2MAMIA68%2FS7oLMbJE66KqmECvRWkWu1%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;2880&quot; height=&quot;1550&quot; data-filename=&quot;스크린샷 2021-04-17 오후 8.19.43.png&quot; data-origin-width=&quot;2880&quot; data-origin-height=&quot;1550&quot;/&gt;&lt;/span&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; '특정IP에서만 실행'&lt;/b&gt; 에 프로그램을 돌릴 PC 의 IP 를 넣어준다(현재 PC의 IP 는 구글 검색창에&lt;b&gt; 'what is my IP'&lt;/b&gt; 을 검색하면 쉽게 알 수 있다). &lt;span style=&quot;color: #333333;&quot;&gt;&lt;span&gt;&amp;nbsp;그 후&amp;nbsp;&lt;/span&gt;'Open API key 발급받기' 를 클릭하면 &lt;b&gt;Access key, Secret Key&lt;/b&gt; 를 발급하여 주는데 이 &lt;b&gt;두 개의 key 를 잘 저장해두도록 한다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;API 사용 (pyupbit)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;업비트에서 제공하는 Open API 목록은 '업비트 개발자 센터&lt;span&gt;(&lt;/span&gt;&lt;a href=&quot;https://docs.upbit.com&quot;&gt;https://docs.upbit.com&lt;/a&gt;&lt;span&gt;)' 에 자세히 나와있으니 시간이 있으면 개인적으로 둘러보고, 여기서는 &lt;b&gt;pyupbit&lt;/b&gt; 라는 업비트 API 사용을 간편하게 도와주는 오픈소스 소프트웨어를 사용한다. 이름에서 알 수 있듯이 '파이썬' 기반의 오픈소스이기 때문에 최신 버전의 python3 를 미리 설치해주어야 한다(파이썬 설치는 각자 진행하도록 하자).&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;&lt;span&gt;pyupbit 를 설치&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1618659428850&quot; class=&quot;r&quot; data-ke-language=&quot;r&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ pip install pyupbit
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;pyjwt 모듈 설치&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1618659485620&quot; class=&quot;r&quot; data-ke-language=&quot;r&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ pip install pyjwt
&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;span&gt;설치가 끝났으면 pyupbit 모듈을 import 하여 사용할 수 있다. 자세한 사용법은 pyupbit(&lt;a href=&quot;https://github.com/sharebook-kr/pyupbit&quot;&gt;github.com/sharebook-kr/pyupbit&lt;/a&gt;) 사이트를 참조하자.&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-filename=&quot;스크린샷 2021-04-17 오후 9.29.05.png&quot; data-origin-width=&quot;1046&quot; data-origin-height=&quot;600&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbPfHB/btq2QUCZpZb/R8GQClyBe9H1YKF21WZYtk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbPfHB/btq2QUCZpZb/R8GQClyBe9H1YKF21WZYtk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbPfHB/btq2QUCZpZb/R8GQClyBe9H1YKF21WZYtk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbPfHB%2Fbtq2QUCZpZb%2FR8GQClyBe9H1YKF21WZYtk%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;494&quot; height=&quot;600&quot; data-filename=&quot;스크린샷 2021-04-17 오후 9.29.05.png&quot; data-origin-width=&quot;1046&quot; data-origin-height=&quot;600&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;widthContent&quot; data-filename=&quot;스크린샷 2021-04-17 오후 9.35.07.png&quot; data-origin-width=&quot;204&quot; data-origin-height=&quot;238&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKaYl6/btq2MloUicS/h6oxcPq4VJJvgm2MPRw1h0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKaYl6/btq2MloUicS/h6oxcPq4VJJvgm2MPRw1h0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKaYl6/btq2MloUicS/h6oxcPq4VJJvgm2MPRw1h0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKaYl6%2Fbtq2MloUicS%2Fh6oxcPq4VJJvgm2MPRw1h0%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;115&quot; height=&quot;238&quot; data-filename=&quot;스크린샷 2021-04-17 오후 9.35.07.png&quot; data-origin-width=&quot;204&quot; data-origin-height=&quot;238&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;widthContent&quot; data-filename=&quot;스크린샷 2021-04-17 오후 9.39.22.png&quot; data-origin-width=&quot;196&quot; data-origin-height=&quot;198&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rypEN/btq2MlCt2iu/Fp6ESUskcqtXb8cgPKWsKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rypEN/btq2MlCt2iu/Fp6ESUskcqtXb8cgPKWsKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rypEN/btq2MlCt2iu/Fp6ESUskcqtXb8cgPKWsKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrypEN%2Fbtq2MlCt2iu%2FFp6ESUskcqtXb8cgPKWsKk%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;145&quot; height=&quot;198&quot; data-filename=&quot;스크린샷 2021-04-17 오후 9.39.22.png&quot; data-origin-width=&quot;196&quot; data-origin-height=&quot;198&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방법은 코드의 동작만을 보여주기 위한 매우 기본적인 아이디어로 실제 구현해보면 매우 많은 거래로 인해 수수료로 투자금이 모두 사라지는 것을 볼 수 있을 것이다.,, 구현은 아래와 같다!&lt;/p&gt;
&lt;pre id=&quot;code_1618660577406&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import pyupbit
import math
import time

upbit = pyupbit.Upbit(`Access Key`, `Secret Key`)	# 자신의 key 값으로 변경

# 차트에서 최근 count 개의 평균값을 반환
def get_average(coin, count, interval) :		
    df = pyupbit.get_ohlcv(coin, count = count, interval = interval)
    avg = sum(df.close) / count
    return avg

# 시장가로 모두 매도
def sell_market_all(coin) :
    balance = upbit.get_balance(coin)
    if balance &amp;gt; 0 :
        upbit.sell_market_order(coin, balance)

# 시장가로 모두 매수
def buy_market_all(coin) :
    balance = math.floor(upbit.get_balance(&quot;KRW&quot;)) - 50
    if balance &amp;gt;= 5000 :
        upbit.buy_market_order(coin, balance)


if __name__ == '__main__': 
    try:
        coin = &quot;KRW-BTC&quot;
        count = 5
        interval = &quot;minute15&quot;

        while True :
            cur_price = pyupbit.get_current_price(coin)			# 현재가
            dest_price = get_average(coin, count, interval)		# 목표가
            
            # 현재가가 목표가보다 낮아지면 매도
            if cur_price &amp;lt; dest_price :
            	sell_market_all(coin)
            	time.sleep(1)
            
            # 현재가가 목표가보다 높아지면 매수
            else :
            	buy_market_all(coin)
            	time.sleep(1)
            time.sleep(1)

    except Exception as ex:
        sys.exit(0)&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;&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;</description>
      <category>프로젝트/가상화폐 자동투자</category>
      <category>가상화폐</category>
      <category>비트코인</category>
      <category>빗썸</category>
      <category>업비트</category>
      <category>자동투자</category>
      <category>재테크</category>
      <author>aa_rong_blog</author>
      <guid isPermaLink="true">https://poalim.tistory.com/27</guid>
      <comments>https://poalim.tistory.com/27#entry27comment</comments>
      <pubDate>Sat, 17 Apr 2021 18:35:19 +0900</pubDate>
    </item>
    <item>
      <title>[Pintos]  Project 1 : Thread(스레드) Background- 진행흐름, Kernel thread, 코드분석, Registers, assembly, switch.S 분석</title>
      <link>https://poalim.tistory.com/26</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이 포스트에서는 본격적인 프로젝트에 들어가기 위해 꼭 필요한 개념들을 잡아두기 위해 여러 가지 배경 지식들을 설명한다. 이는 pintos kernel 에서 threads 의 기본적인 흐름도, 범용 레지스터 개념, struct thread 의 구조, 주어진 코드 분석(thread.h, thread.c, switch.h, switch.S) 등이 포함된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;진행흐름&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전 포스트에서 살펴봤다시피 pintos 의 loader 는 우리를 init.c 의 main 함수로 가져다놓는다. 이 main 함수를 살펴보면 여러 초기화 작업 후에 가장 먼저 나오는 작업은 thread 의 시작이다. 코드에서 한 번 살펴보자.&lt;/p&gt;
&lt;pre id=&quot;code_1617940018408&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* thread/init.c        */
/* Pintos main program. */
int main(void){
    ...
    thread_init();
    ...
    ...
    thread_start();
    ...
    ...
    thread_exit();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 가지 유의해야 할 점은, 여기서 말하는 thread 란 우리가 평소에 보아왔던 user thread 와는 다른 것이라는 것이다. pintos 에서 user thread 는 user_thread 라고 표시되어 있으며 계속해서 언급되는 thread 는 항상 kernel thread 를 말한다. 그렇다면 kernel thread 의 개념부터 알아보도록 하자. thread 와 관련된 함수들은 &amp;lt;thread.h&amp;gt;, &amp;lt;thread.c&amp;gt; 에 작성되어 있다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Kernel thread&lt;/h4&gt;
&lt;pre id=&quot;code_1617940513489&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* thread/thread.c */
/* Function used as the basis for a kernel thread. */
static void
kernel_thread (thread_func *function, void *aux) 
{
  ASSERT (function != NULL);

  intr_enable ();       /* The scheduler runs with interrupts off. */
  function (aux);       /* Execute the thread function. */
  thread_exit ();       /* If function() returns, kill the thread. */
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 함수가 thread 동작의 전부이다. 보면 알겠지만 사실상 하는 것이라고는 function 하나를 실행시키는 것이 전부이다. 파라미터를 보면, thread_func *function 은 이 kernel 이 실행할 함수를 가르키고, void *aux 는 보조 파라미터로 synchronization 을 위한 세마포 등이 들어온다. 여기서 실행시키는 function 은 이 thread 가 종료될때가지 실행되는 main 함수라고 생각할 수 있다. 즉, 이 function 은 idle thread 라고 불리는 thread 를 하나 실행시키는데, 이 idle thread 는 하나의 c 프로그램에서 하나의 main 함수 안에서 여러 함수호출들이 이루어지는 것처럼, pintos kernel 위에서 여러 thread 들이 동시에 실행될 수 있도록 하는 단 하나의 main thread 인 셈이다. 우리의 목적은 이 idle thread 위에 여러 thread 들이 &lt;b&gt;동시에&lt;/b&gt; 실행되도록 만드는 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-filename=&quot;스크린샷 2021-04-09 오후 1.41.07.png&quot; data-origin-width=&quot;574&quot; data-origin-height=&quot;494&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Rqs4b/btq18EbHqaz/plmLlmvq3vrshuRlgOKjt1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Rqs4b/btq18EbHqaz/plmLlmvq3vrshuRlgOKjt1/img.png&quot; data-alt=&quot;kernel_thread 의 동작&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Rqs4b/btq18EbHqaz/plmLlmvq3vrshuRlgOKjt1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRqs4b%2Fbtq18EbHqaz%2FplmLlmvq3vrshuRlgOKjt1%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;347&quot; height=&quot;494&quot; data-filename=&quot;스크린샷 2021-04-09 오후 1.41.07.png&quot; data-origin-width=&quot;574&quot; data-origin-height=&quot;494&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;kernel_thread 의 동작&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;위의 init.c 의 main 코드에서 보면, thread_init() 에서 thread 에 들어가야 하는 여러 정보들을 초기화 해준 후 thread_start() 에서 idle thread 를 만들어주면서 본격적으로 thread 를 시작한다.&lt;/p&gt;
&lt;pre id=&quot;code_1617943790215&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* thread.c */
void
thread_start (void) 
{
  /* Create the idle thread. */
  struct semaphore idle_started;
  sema_init (&amp;amp;idle_started, 0);
  thread_create (&quot;idle&quot;, PRI_MIN, idle, &amp;amp;idle_started);

  /* Start preemptive thread scheduling. */
  intr_enable ();

  /* Wait for the idle thread to initialize idle_thread. */
  sema_down (&amp;amp;idle_started);
}



static void
idle (void *idle_started_ UNUSED) 
{
  struct semaphore *idle_started = idle_started_;
  idle_thread = thread_current ();
  sema_up (idle_started);

  for (;;) 
    {
      /* Let someone else run. */
      intr_disable ();
      thread_block ();

      asm volatile (&quot;sti; hlt&quot; : : : &quot;memory&quot;);
    }
}&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;thread_start() 에서 thread_create 을 하는 순간 idle thread 가 생성되고 동시에 idle 함수가 실행된다. idle thread 는 여기서 한 번 schedule 을 받고 바로 sema_up 을 하여 thread_start() 의 마지막 sema_down 을 풀어주어 thread_start 가 작업을 끝내고 run_action() 이 실행될 수 있도록 해주고 idle 자신은 block 된다. idle thread 는 pintos 에서 실행 가능한 thread 가 하나도 없을 때 이 wake 되어 다시 작동하는데, 이는 CPU 가 무조건 하나의 thread 는 실행하고 있는 상태를 만들기 위함이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Thread 진행흐름&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;1986&quot; data-origin-height=&quot;1282&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfxmVe/btq2gDBM7ej/fI4G9JSBUBpz8whIHORvP0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfxmVe/btq2gDBM7ej/fI4G9JSBUBpz8whIHORvP0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfxmVe/btq2gDBM7ej/fI4G9JSBUBpz8whIHORvP0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfxmVe%2Fbtq2gDBM7ej%2FfI4G9JSBUBpz8whIHORvP0%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;651&quot; height=&quot;1282&quot; data-origin-width=&quot;1986&quot; data-origin-height=&quot;1282&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Thread 는 RUNNING, READY, BLOCKED, DYING 의 4가지 상태를 가진다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;THREAD_RUNNING : 현재 실행중인 스레드를 말하고 한 번에 하나의 스레드만 running 상태를 가질 수 있다.&lt;/li&gt;
&lt;li&gt;THREAD_READY : 실행될 준비가 되어 실행을 기다리는 상태이며, 현재 실행중인 스레드가 실행을 멈추면 이 상태에 있는 스레드 중 하나가 실행 상태로 들어간다. 이 상태에 있는 스레드들은 READY QUEUE 에 들어가 관리된다.&lt;/li&gt;
&lt;li&gt;THREAD_BLOCKED : 말 그대로 멈춰서 아직 실행 불가능한 상태이며, thread_unblock() 을 통해 실행 가능한 상태가 되면 THREAD_READY 상태가 되어 ready queue 에 들어간다.&lt;/li&gt;
&lt;li&gt;THREAD_DYING : 스레드의 실행이 모두 끝나고 종료된 상태이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 흐름에서 ready queue 에 스레드들을 넣고 뺄 때의 순서를 정하는 것이 scheduling 의 주된 작업이라고 할 수 있다. 현재 pintos 는 round-robin 방식으로 구현되어 있고, 이는 어떤 스레드가 THREAD_READY 상태가 되어 ready queue 에 들어갈 때 queue 의 맨 뒤로 들어가고 next_thread_to_run() 에서 다음 running 상태가 될 스레드를 고를 때는 queue 의 맨 앞의 스레드를 골라서 queue 에 들어온 순서대로 실행되는 방식이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;

&lt;h2 data-ke-size=&quot;size26&quot;&gt;Scheduling&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 스레드를 실행시키다가 다른 스레드로 CPU 의 소유권을 넘겨야 할 때 scheduling 이 일어난다. 이 scheduling 이 일어나는 순간은 위의 스레드 흐름도에서 볼 수 있듯이 thread_yield(), thread_block(), thread_exit() 함수가 실행될 때이다. 이 함수들은 현재 실행중인 스레드에서 호출해주어야 하는데, 실행중인 스레드가 이 함수들을 실행시키지 않고 계속 CPU 소유권을 가지는 것을 방지하기 위하여 추가로 한 가지 장치가 있다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1617947229800&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* devices/timer.c */
static void
timer_interrupt (struct intr_frame *args UNUSED)
{
  ticks++;
  thread_tick ();
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1617947252286&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* thread/thread.c */
void
thread_tick (void) 
{
  struct thread *t = thread_current ();

  /* Update statistics. */
  if (t == idle_thread)
    idle_ticks++;
#ifdef USERPROG
  else if (t-&amp;gt;pagedir != NULL)
    user_ticks++;
#endif
  else
    kernel_ticks++;

  /* Enforce preemption. */
  if (++thread_ticks &amp;gt;= TIME_SLICE)
    intr_yield_on_return ();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;timer 인터럽트는 매 tick 마다 ticks 라는 변수를 증가시킴으로서 시간을 잰다. 이렇게 증가한 ticks 가 TIME_SLICE 보다 커지는 순간에 intr_yield_on_return() 이라는 인터럽트가 실행되는데, 이 인터럽트는 결과적으로 thread_yield() 를 실행시킨다. 즉 위의 scheduling 함수들이 호출되지 않더라고 일정 시간마다 자동으로 scheduling 이 발생한다.&lt;/p&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;이제 scheduling 함수가 어떻게 작동하는지를 살펴보자. 사실 이 함수의 작동을 정확하게 이해하는 것이 thread system 의 가장 핵심이다. scheduling 함수는 thread_yield(), thread_block(), thread_exit() 함수 내의 거의 마지막 부분에 실행되어 CPU 의 소유권을 현재 실행중인 스레드에서 다음에 실행될 스레드로 넘겨주는 작업을 한다. 코드를 한 번 살펴보자.&lt;/p&gt;
&lt;pre id=&quot;code_1617948104840&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// &amp;lt;thread/thread.c&amp;gt;
static void
schedule (void) 
{
  struct thread *cur = running_thread ();
  struct thread *next = next_thread_to_run ();
  struct thread *prev = NULL;

  ASSERT (intr_get_level () == INTR_OFF);
  ASSERT (cur-&amp;gt;status != THREAD_RUNNING);
  ASSERT (is_thread (next));

  if (cur != next)
    prev = switch_threads (cur, next);
  thread_schedule_tail (prev);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서부터는 설명의 편의를 위해 현재 실행중인 thread 를 thread A 라고 하고, 다음에 실행될 스레드를 thread B 라고 하겠다. *cur 은 thread A, *next 는 next_thread_to_run() 이라는 함수(ready queue 에서 다음에 실행될 스레드를 골라서 return 함. 지금은 round-robin 방식으로 고른다.)에 의해 thread B 를 가르키게 되고, *prev 는 thread A 가 CPU 의 소유권을 thread B 에게 넘겨준 후 thread A 를 가리키게 되는 포인터다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ASSERT (intr_get_level () == INTR_OFF) : scheduling 도중에는 인터럽트가 발생하면 안 되기 때문에 INTR_OFF 상태인지 확인한다.&lt;/li&gt;
&lt;li&gt;ASSERT (cur-&amp;gt;status != THREAD_RUNNING) : CPU 소유권을 넘겨주기 전에 running 스레드는 그 상태를 running 외의 다른 상태로 바꾸어주는 작업이 되어 있어야 하고 이를 확인하는 부분이다.&lt;/li&gt;
&lt;li&gt;ASSERT (is_thread (next)) : next_thread_to_run() 에 의해 올바른 thread 가 return 되었는지 확인한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 후 새로 실행할 스레드가 정해졌다면 &lt;b&gt;switch_thread (cur, next)&lt;/b&gt; 명령어를 실행한다. 이 함수는 &lt;b&gt;&amp;lt;thread/switch.S&amp;gt;&lt;/b&gt; 에 assembly 언어로 작성되어 있다. 이 함수가 사실상 핵심이기에 내용을 뜯어보기 전에 이 코드를 해석하려면 범용 레지스터와 assembly 언어에 대한 기본적인 이해가 필요할 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;범용 레지스터&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-filename=&quot;스크린샷 2021-04-09 오전 9.10.37.png&quot; data-origin-width=&quot;1258&quot; data-origin-height=&quot;678&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bZbgY3/btq2bK26Wjd/lk5nlbYhmvtshISDBSa310/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bZbgY3/btq2bK26Wjd/lk5nlbYhmvtshISDBSa310/img.png&quot; data-alt=&quot;CPU 의 구성&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bZbgY3/btq2bK26Wjd/lk5nlbYhmvtshISDBSa310/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbZbgY3%2Fbtq2bK26Wjd%2Flk5nlbYhmvtshISDBSa310%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;568&quot; height=&quot;678&quot; data-filename=&quot;스크린샷 2021-04-09 오전 9.10.37.png&quot; data-origin-width=&quot;1258&quot; data-origin-height=&quot;678&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;CPU 의 구성&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 사용하는 CPU 는 위 그림처럼 구성되어 있다. Control Unit 은 CPU 를 제어하는 장치이고, I/O Unit 은 입출력 장치를 다루고, ALU 는 여러 연산을 처리하는 장치이다. 그 외에 CPU 에도 Register 라는 아주 작은 저장소들이 있는데, 이들은 CPU 가 데이터에 접근하는 가장 빠른 방법을 제공한다. x86 장치에서 CPU 는 (그 밖에도 다른 레지스터들이 존재하지만) 다음의 &lt;b&gt;8개의 범용 레지스터&lt;/b&gt;가 CPU 의 효율적인 일처리를 위해 각기 용도에 맞게 설계되어 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;EAX(Extended Accumulator Register) : 산술연산 또는 &lt;b&gt;함수의 리턴 값&lt;/b&gt; 저장. 이 레지스터의 값을 살펴보면 함수의 리턴 여부, 리턴 값을 알 수 있다.&lt;/li&gt;
&lt;li&gt;EDX(Extended Data Register) : EAX 의 확장 개념으로 복잡한 연산의 추가 데이터 저장을 위해 사용된다.&lt;/li&gt;
&lt;li&gt;ECX(Extended Counter Register) : 반복적으로 수행되는 연산에 사용되는 &lt;b&gt;카운터 값&lt;/b&gt;이 저장된다. 항상 감소되는 방향으로 설계되어 있다.&lt;/li&gt;
&lt;li&gt;ESI(Extended Source Index) : 데이터 연산에서 데이터의 source, 즉 입력 데이터의 앖 혹은 입력 스트림의 위치를 나타낸다. 주로 Read 작업을 위해 사용된다.&lt;/li&gt;
&lt;li&gt;EDI(Extended Destination Index) : 데이터 연산에서 데이터의 destination, 즉 연산의 결과값의 위치를 나타낸다. 주로 Write 작업을 위해 사용된다.&lt;/li&gt;
&lt;li&gt;ESP(Extended Stack Pointer) : &lt;b&gt;스택의 가장 높은(최근 PUSH된) 위치&lt;/b&gt;를 가리킨다. 함수 호출 시 리턴 주소가 가장 마지막에 PUSH 되므로 함수 호출 직후 ESP 는 보통 리턴 주소를 가리킨다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;EBP(Extended Base Pointer) : ESP 와 반대로 스택의 시작점을 가리킨다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;EBX : 특정 용도 X, 추가적인 저장소로써 사용된다.&lt;/li&gt;
&lt;li&gt;EIP : 현재 실행중인 명령의 주소를 가리킨다.&amp;nbsp;&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;각 스레드는 하나의 page(4KB) 에 저장된다. 그 구조는 &lt;b&gt;&amp;lt;thread/thread.h&amp;gt;&lt;/b&gt; 에 저장되어 있고, 아래와 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-filename=&quot;스크린샷 2021-04-09 오후 3.31.26.png&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;1020&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgl9fE/btq2dBd13K3/YkY1OkzzEKmaCvbs4ZHZDK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgl9fE/btq2dBd13K3/YkY1OkzzEKmaCvbs4ZHZDK/img.png&quot; data-alt=&quot;Thread Structure&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgl9fE/btq2dBd13K3/YkY1OkzzEKmaCvbs4ZHZDK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbgl9fE%2Fbtq2dBd13K3%2FYkY1OkzzEKmaCvbs4ZHZDK%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;269&quot; height=&quot;1020&quot; data-filename=&quot;스크린샷 2021-04-09 오후 3.31.26.png&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;1020&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Thread Structure&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스레드는 스레드의 정보를 저장하는 TCB 영역과, 여러 정보를 저장하는 kernel stack 영역으로 나누어져 있고, TCB 영역은 offset 0 에서 시작하여 1KB 를 넘지 않는 작은 크기를 차지하고, stack 영역은 나머지 영역을 차지하고 TCB 영역을 침범하지 않기 위해 위에서부터 시작하여 아래 방향으로 저장된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TCB 영역의 구성은 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1617950130923&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct thread
  {
    /* Owned by thread.c. */
    tid_t tid;                          /* Thread identifier. */
    enum thread_status status;          /* Thread state. */
    char name[16];                      /* Name (for debugging purposes). */
    uint8_t *stack;                     /* Saved stack pointer. */
    int priority;                       /* Priority. */
    struct list_elem allelem;           /* List element for all threads list. */

    /* Shared between thread.c and synch.c. */
    struct list_elem elem;              /* List element. */

#ifdef USERPROG
    /* Owned by userprog/process.c. */
    uint32_t *pagedir;                  /* Page directory. */
#endif

    /* Owned by thread.c. */
    unsigned magic;                     /* Detects stack overflow. */
  };&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 &lt;b&gt;*stack 이라고 표현된 포인터&lt;/b&gt;가 중요한데, 실행중인 스레드 thread A 에서 scheduling 이 일어나면, 현재 CPU 가 실행중이던 상태, 즉 현재 CPU 의 Register 들에 저장된 값들을 이 stack 에 저장한다. 후에 다시 thread A 가 실행되는 순간에 이 stack 의 값들이 다시 CPU 의 Register 로 로드되어 실행을 이어갈 수 있게 된다. 이 stack 은 TCB 위의 부분 kernel stack 의 특정 부분에 저장되기 때문에 *stack 포인터는 kernel stack 의 특정 부분을 가리킨다.&lt;/p&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;&amp;lt;switch.S&amp;gt;&lt;/b&gt; 의 코드를 분석해보도록 하자.&lt;/p&gt;
&lt;pre id=&quot;code_1617950729641&quot; class=&quot;r&quot; style=&quot;display: block; overflow: auto; padding: 15px; color: #383a42; background: #f6f7f8; font-size: 14px; border-radius: 3px; font-family: Menlo, Consolas, Monaco, monospace; border: 1px solid #dddddd; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;r&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.globl switch_threads
.func switch_threads
switch_threads:
	# Save caller's register state.
	#
	# Note that the SVR4 ABI allows us to destroy %eax, %ecx, %edx,
	# but requires us to preserve %ebx, %ebp, %esi, %edi.  See
	# [SysV-ABI-386] pages 3-11 and 3-12 for details.
	#
	# This stack frame must match the one set up by thread_create()
	# in size.
	pushl %ebx
	pushl %ebp
	pushl %esi
	pushl %edi

	# Get offsetof (struct thread, stack).
.globl thread_stack_ofs
	mov thread_stack_ofs, %edx

	# Save current stack pointer to old thread's stack, if any.
	movl SWITCH_CUR(%esp), %eax
	movl %esp, (%eax,%edx,1)

	# Restore stack pointer from new thread's stack.
	movl SWITCH_NEXT(%esp), %ecx
	movl (%ecx,%edx,1), %esp

	# Restore caller's register state.
	popl %edi
	popl %esi
	popl %ebp
	popl %ebx
        ret
.endfunc&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;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Assembly 언어&lt;/h3&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;b&gt;(%eax) 와 %eax 는 엄연히 다른 역할&lt;/b&gt;을 한다. 괄호가 붙으면 해당 레지스터의 값이 아니라, 해당 레지스터에는 가상 메모리에서의 메모리 주소가 들어있고, 해당 메모리 주소의 값을 나타낸다. 즉 &lt;b&gt;포인터&lt;/b&gt;이다.
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;예를들어, %eax 에 0x0000CD 라는 값이 있고, 현재 진행중인 프로세스의 가상 메모리의 0x0000CD 주소에 8 이라는 값이 저장되어 있다면, %eax = 0x0000CD, (%eax) = 8 이 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;assembly 언에서 사용되는 상수의 경우에는 기본적으로 해당 주소의 메모리 값을 나타낸다. 해당 &lt;b&gt;상수 자체를 나타내기 위해서는 '$' 을 앞에 붙인다.&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;$8 : 상수 8 을 나타냄&lt;/li&gt;
&lt;li&gt;8 : 메모리의 0x8 주소에 있는 값을 나타냄&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;add, sub, mov 등의 명령어 뒤에 붙는 &lt;b&gt;b, w, l 등은 변수의 크기를 나타낸다.&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;movb : byte 단위의 명령&lt;/li&gt;
&lt;li&gt;movw : word(4byte) 단위의 명령&lt;/li&gt;
&lt;li&gt;movl : double word(8byte) 단위의 명령&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;괄호의 활용
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;항이 2개인 경우 : (%eax, %ebx) =&amp;gt; (%eax + %ebx) =&amp;gt; %eax 레지스터의 값 + %ebx 레지스터의 값 의 결과가 가리키는 메모리 주소의 값&lt;/li&gt;
&lt;li&gt;항이 3개인 경우 : (%eax, %ebx, 3) =&amp;gt; (%eax + %ebx * 3) =&amp;gt; 마지막 항은 두번째 항에 곱하기로 적용&lt;/li&gt;
&lt;li&gt;괄호 앞에 상수 : 8(%eax) =&amp;gt; (8 + %eax) =&amp;gt; 상수를 더한 결과가 가리키는 주소의 값&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;mov, push, pop 명령어의 용도
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;movl&amp;nbsp; A&amp;nbsp; B : A 의 값을 B 에 저장한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;pushl A : A 의 값을 stack 에 저장, (ex. pushl&amp;nbsp; %eax&amp;nbsp; =&amp;gt; %eax 레지스터의 값이 stack 에 저장됨)&lt;/li&gt;
&lt;li&gt;popl A : stack 에서 값을 하나 제거하면서 A 에 저장, (ex. popl&amp;nbsp; %eax&amp;nbsp; =&amp;gt; stack 의 맨 위의 값을 %eax 레지스터에 저장함)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;ret : return&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 scheduling() 함수에서 switch_thread 함수의 호출부터 보자.&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;prev = switch_threads (cur, next);&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 cur (thread A), next (thread B) 를 파라미터로 받으며 함수가 호출된다. 함수가 호출되면 kernel stack 에는 함수의 파라미터와 return 주소가 차례로 저장된다. 즉 아래와 같은 상태가 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-filename=&quot;스크린샷 2021-04-09 오후 4.12.47.png&quot; data-origin-width=&quot;952&quot; data-origin-height=&quot;936&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zvYV4/btq2hfgvZaY/vpj2Bg05AX25Tv6zhP4xk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zvYV4/btq2hfgvZaY/vpj2Bg05AX25Tv6zhP4xk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zvYV4/btq2hfgvZaY/vpj2Bg05AX25Tv6zhP4xk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzvYV4%2Fbtq2hfgvZaY%2Fvpj2Bg05AX25Tv6zhP4xk1%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;418&quot; height=&quot;936&quot; data-filename=&quot;스크린샷 2021-04-09 오후 4.12.47.png&quot; data-origin-width=&quot;952&quot; data-origin-height=&quot;936&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;switch_threads 의 코드에서 &lt;b&gt;pushl&amp;nbsp; %ebx&amp;nbsp; ~&amp;nbsp; pushl&amp;nbsp; %edi&lt;/b&gt; 를 실행하면 현재 스택포인터(ESP) 에서부터 값들이 push 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-filename=&quot;스크린샷 2021-04-09 오후 4.22.01.png&quot; data-origin-width=&quot;724&quot; data-origin-height=&quot;734&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lLQJ7/btq2gRGX4Pl/WWWlgkREK9hpDj1502MSq1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lLQJ7/btq2gRGX4Pl/WWWlgkREK9hpDj1502MSq1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lLQJ7/btq2gRGX4Pl/WWWlgkREK9hpDj1502MSq1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlLQJ7%2Fbtq2gRGX4Pl%2FWWWlgkREK9hpDj1502MSq1%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;421&quot; height=&quot;734&quot; data-filename=&quot;스크린샷 2021-04-09 오후 4.22.01.png&quot; data-origin-width=&quot;724&quot; data-origin-height=&quot;734&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 kernel stack 이 모두 채워지고 이 stack frame 의 모습은 &lt;b&gt;&amp;lt;thread/switch.h&amp;gt;&lt;/b&gt; 에서 아래와 같이 찾아볼 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1617953042815&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct switch_threads_frame 
  {
    uint32_t edi;               /*  0: Saved %edi. */
    uint32_t esi;               /*  4: Saved %esi. */
    uint32_t ebp;               /*  8: Saved %ebp. */
    uint32_t ebx;               /* 12: Saved %ebx. */
    void (*eip) (void);         /* 16: Return address. */
    struct thread *cur;         /* 20: switch_threads()'s CUR argument. */
    struct thread *next;        /* 24: switch_threads()'s NEXT argument. */
  };&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오른쪽 주석으로 달린 0, 4, 8, ... 은 offset 을 말하고, 기준은 ESP 이다.&lt;/p&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 id=&quot;code_1617953146591&quot; class=&quot;r&quot; data-ke-language=&quot;r&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;	# Get offsetof (struct thread, stack).
.globl thread_stack_ofs
	mov thread_stack_ofs, %edx&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분인데, 여기서 thread_stack_ofs 가 뭔가 싶을 수 있다. 이 값은 thread 의 시작 주소와 stack 포인터가 있는 주소 사이의 간격(offset) 을 나타내고 &lt;b&gt;&amp;lt;thread.c&amp;gt;&lt;/b&gt;&amp;nbsp;의 마지막줄에 아래와 같이 구현되어 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1617953356358&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* Offset of `stack' member within `struct thread'.
   Used by switch.S, which can't figure it out on its own. */
uint32_t thread_stack_ofs = offsetof (struct thread, stack);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 나타내는 stack 포인터를 kernel stack 이라고 생각하면 큰일난다. 당연히 TCB 안에 있는 *stack 을 나타내는 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-filename=&quot;스크린샷 2021-04-09 오후 4.37.26.png&quot; data-origin-width=&quot;722&quot; data-origin-height=&quot;736&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IDA3C/btq2cnglGTw/ryWoKRFYK3v7MizyRc2oj0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IDA3C/btq2cnglGTw/ryWoKRFYK3v7MizyRc2oj0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IDA3C/btq2cnglGTw/ryWoKRFYK3v7MizyRc2oj0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIDA3C%2Fbtq2cnglGTw%2FryWoKRFYK3v7MizyRc2oj0%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;438&quot; height=&quot;736&quot; data-filename=&quot;스크린샷 2021-04-09 오후 4.37.26.png&quot; data-origin-width=&quot;722&quot; data-origin-height=&quot;736&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;mov&amp;nbsp; thread_stack_ofs&amp;nbsp; %edx&lt;/b&gt; 를 하였으므로 이 값을 &lt;b&gt;%edx 레지스터&lt;/b&gt;에 저장한다. 이 값은 thread 주소와 더하여 stack 포인터의 위치를 알아내는 데 사용된다.&lt;/p&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 id=&quot;code_1617954093086&quot; class=&quot;r&quot; data-ke-language=&quot;r&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Save current stack pointer to old thread's stack, if any.
movl SWITCH_CUR(%esp), %eax
movl %esp, (%eax,%edx,1)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SWITCH_CUR 이 생소하다. 이것은 &lt;b&gt;&amp;lt;switch.h&amp;gt;&lt;/b&gt; 에서 찾아볼 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1617954169519&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#define SWITCH_CUR      20
#define SWITCH_NEXT     24&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SWITCH_CUR, SWITCH_NEXT 는 20, 24 를 나타내는 상수라는 걸 알 수 있다. 그러면 위의 식은 아래와 나타낼 수 있다.&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;movl&amp;nbsp; 20(%esp),&amp;nbsp; %eax =&amp;gt; movl&amp;nbsp; (20 + %esp),&amp;nbsp; %eax&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;20 + esp 은 위의 thread 그림에서 볼 수 있듯이 *cur 의 값이 저장되어 있는 주소이다. 헷갈린다면 위에 잠깐 언급한 switch_thread_frame 을 참고하자. 즉 kernel stack 에서 &lt;b&gt;*cur 에 저장되어 있는 값을 %eax 로 저장&lt;/b&gt;한다는 의미이다. *cur 에는 처음 switch_thread(cur, next) 함수의 호출 때, 현재 running thread, 즉 thread A 의 주소를 받아와 저장했었다. 즉, &lt;b&gt;현재 실행중인 스레드 thread A 의 주소를 %eax 레지스터에 저장한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;movl&amp;nbsp; %esp,&amp;nbsp; (%eax,&amp;nbsp; %edx,&amp;nbsp; 1)&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것은 현재 %esp 의 값을 (%eax + %edx * 1) 가 가르키는 메모리 주소에 저장하라는 명령이다. 현재 %esp 에는 kernel stack 의 맨 아래부분의 주소가 저장되어 있다. %eax 는 thread_stack_ofs, %edx 는 thread A 의 시작주소 가 저장되어 있으므로 이 두 값을 더하면 thread A 의 TCB 부분의 stack 포인터 위치를 알 수 있다고 위에 언급했었다. 즉 이 stack 포인터 자리에 %esp 에 있는 값이 입력되므로 stack 포인터가 위의 kernel stack 영역의 맨 아래 주소를 가리키게 된다. 그림으로 보면 아래와 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-filename=&quot;스크린샷 2021-04-09 오후 4.58.21.png&quot; data-origin-width=&quot;724&quot; data-origin-height=&quot;732&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HACaE/btq2eLHLbbm/kC8Of5QyvZlxPNwzoBgI5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HACaE/btq2eLHLbbm/kC8Of5QyvZlxPNwzoBgI5K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HACaE/btq2eLHLbbm/kC8Of5QyvZlxPNwzoBgI5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHACaE%2Fbtq2eLHLbbm%2FkC8Of5QyvZlxPNwzoBgI5K%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;403&quot; height=&quot;732&quot; data-filename=&quot;스크린샷 2021-04-09 오후 4.58.21.png&quot; data-origin-width=&quot;724&quot; data-origin-height=&quot;732&quot;/&gt;&lt;/span&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;pre id=&quot;code_1617955173248&quot; class=&quot;r&quot; data-ke-language=&quot;r&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Restore stack pointer from new thread's stack.
movl SWITCH_NEXT(%esp), %ecx
movl (%ecx,%edx,1), %esp&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;SWITCH_NEXT(%esp)&lt;/b&gt; 는 SWITCH_CUR(%esp) 에서 본 것처럼 이번에는 kernel stack 영역의 *next 를 나타내고, 이 값을 %ecx 레지스터에 저장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 줄의 명령은 &lt;b&gt;(%ecx + %edx * 1)&lt;/b&gt; &lt;b&gt;의 메모리 주소의 값을 %esp 에 저장&lt;/b&gt;한다. %ecx 는 다음에 실행할 스레드 thread B 의 시작 주소, %edx 는 thread_stacks_ofs 이므로, 이는 다음에 실행할 스레드 thread B 의 stack 포인터 주소를 %esp 레지스터에 저장함을 말한다. 여기까지 마친 후 각 레지스터에 들어있는 값을 아래 표로 정리하였다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 24.7675%;&quot;&gt;%edx&lt;/td&gt;
&lt;td style=&quot;width: 75.2325%;&quot;&gt;thread_stacks_ofs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 24.7675%;&quot;&gt;%eax&lt;/td&gt;
&lt;td style=&quot;width: 75.2325%;&quot;&gt;SWITCH_CUR(%esp) : 현재 실행중인 thread A 의 주소(*cur)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 24.7675%;&quot;&gt;%esp&lt;/td&gt;
&lt;td style=&quot;width: 75.2325%;&quot;&gt;다음에 실행할 thread B 의 stack 포인터가 가르키는 값 -&amp;gt; thread B 의 kernel stack 영역의 top의 주소&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 24.7675%;&quot;&gt;%ecx&lt;/td&gt;
&lt;td style=&quot;width: 75.2325%;&quot;&gt;SWITCH_NEXT(%esp) : 다음에 실행할 thread B 의 주소(*next)&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;여기까지 실행되면 마법같은 일이 생긴다. 방금전까지 실행되던 레지스터들의 값(%ebx, %ebp, %esi, %edi) 는 모두 thread A 의 구조 안에 저장하였고, %esp 가 thread A 의 kernel stack 영역의 top 을 가리키던 것이 이제 thread B 의 kernel stack 영역의 가장 아래부분(top) 을 가리키게 되면서 자연스럽게 CPU 의 제어가 thread B 로 넘어왔다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-filename=&quot;스크린샷 2021-04-09 오후 5.22.36.png&quot; data-origin-width=&quot;1284&quot; data-origin-height=&quot;664&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BWfRS/btq2gX8oG6G/kRS7XsPU9oEt2ke3YLERj1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BWfRS/btq2gX8oG6G/kRS7XsPU9oEt2ke3YLERj1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BWfRS/btq2gX8oG6G/kRS7XsPU9oEt2ke3YLERj1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBWfRS%2Fbtq2gX8oG6G%2FkRS7XsPU9oEt2ke3YLERj1%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;630&quot; height=&quot;664&quot; data-filename=&quot;스크린샷 2021-04-09 오후 5.22.36.png&quot; data-origin-width=&quot;1284&quot; data-origin-height=&quot;664&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 &lt;b&gt;popl&amp;nbsp; %edi&amp;nbsp; ~&amp;nbsp; popl&amp;nbsp; %ebx &lt;/b&gt;의 명령이 수행되는데, 이제 thread B 로 CPU 의 제어가 넘어갔으므로 thread B 의 kernel stack 영역에 저장되어 있던 edi ~ ebx 의 값이 CPU 의 레지스터로 load 된다. &lt;b&gt;이후의 실행은 thread B 의 제어 아래에서 일어난다.&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;ret&amp;nbsp;&lt;/b&gt;명령어가 실행되어 return 이 되는데, 이 때 return 은 %eax 의 값을 읽어온다고 하였다(%eax 범용 레지스터의 용도가 리턴값을 저장하는 것임). eax 레지스터의 값은 바꾼 적이 없기 때문에 thread A 가 running 상태일 때 저장한 값이 그대로 들어있다. 위에 표를 보면 thread A 의 주소가 저장되어 있다고 쓰여있다. 즉, 이 return 명령이 실행되는 시점은 thread B 의 제어 아래에서 return 되지만 thread A 가 반환되고, 이제는 prev 스레드가 되어버린 thread A 의 주소를 뱉는다. 이 때문에 switch_thread() 의 반환값은 thread A 의 주소가 되고 schedule() 에서 &lt;b&gt;prev = switch_thread(cur, next);&lt;/b&gt; 로 호출했기 때문에 prev 가 전에 실행중이던 스레드인 thread 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;이제 Pintos project 1 - Thread 를 시작하기 위한 배경지식은 모두 설명하였다. 다음 포스트에서는 실제 구현을 해본다.&lt;/p&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;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;Reference&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;thread status : &lt;a href=&quot;https://h2oche.github.io/2019/03/12/pintos-guide-1/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;h2oche.github.io/2019/03/12/pintos-guide-1/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;범용 CPU 레지스터 : &lt;a href=&quot;https://thinkpro.tistory.com/107?category=562773&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;thinkpro.tistory.com/107?category=562773&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프로젝트/Pintos</category>
      <category>Multi-thread</category>
      <category>os</category>
      <category>pintos</category>
      <category>synchronization</category>
      <category>Thread</category>
      <category>범용레지스터</category>
      <category>운영체제</category>
      <author>aa_rong_blog</author>
      <guid isPermaLink="true">https://poalim.tistory.com/26</guid>
      <comments>https://poalim.tistory.com/26#entry26comment</comments>
      <pubDate>Fri, 9 Apr 2021 17:32:14 +0900</pubDate>
    </item>
    <item>
      <title>[Pintos] 4.4BSD 스케줄러(4.4BSD Scheduler) - niceness, priority, recent_cpu, load_avg, fixed-point real arithmetic</title>
      <link>https://poalim.tistory.com/25</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;멀티스레드 프로그램에서는 구조가 상이한 여러 스레드들이 함께 존재한다. 어떤 스레드는 다수의 I/O 요청을 처리하지만 복잡한 연산 작업은 없을 수 있으며, 어떤 스레드는 I/O 요청 없이 다수의 복잡한 연산 작업 만으로 이루어져 있을 수 있다. 위의 첫 번째 스레드는 I/O 장비와의 빠른 반응속도를 필요로 하지만 CPU time 은 거의 필요하지 않지만, 뒤의 스레드는 반대이다. 이렇게 다양한 스레드들이 존재하기 때문에, 효율적인 프로그램을 위해서 &lt;b&gt;Scheduler(스케줄러)&lt;/b&gt; 는 이러한 요구들이 최대한 효율적으로 사용되도록 배치해야 한다. 이를 위해 스케줄러는 priority 라는 속성을 관리하는데, 이 속성에 기반하여 스레드들의 처리 순서를 결정한다. 이 포스트에서는 4.4BSD Scheduler 스케줄러가 각 스레드의 priority 를 관리하는 방법을 공부한다.&lt;/p&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.4BSD 스케줄러는 multilevel feedback queue scheduler 의 구성을 갖는다. 이러한 방식에서 ready queue 가 여러개 존재하고, 각각의 ready queue 는 서로 다른 priority 를 가진다. 실행중이던 스레드가 실행을 끝내면, 스케줄러는 가장 priority 가 높은 ready queue 에서부터 하나의 스레드를 꺼내와 실행시키고, 해당 ready queue 에 여러 스레드들이 있는 경우 &quot;round robin&quot; 방식으로 스레드들을 실행시킨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* round robin : 스레드 간에 우선순위를 두지 않고, 순서대로 정해진 시간단위로 CPU 를 할당하는 방식의 스케줄링 알고리즘이다. 시간단위만큼 실행된 스레드는 리스트의 맨 뒤로 이동한다.&lt;/p&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;h3 data-ke-size=&quot;size23&quot;&gt;1. &lt;span style=&quot;background-color: #f3c000;&quot;&gt;Niceness&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 스레드는 이 스레드가 다른 스레드에 대해 어떠한 성질을 가지는지를 나타내는 &lt;b&gt;정수의 nice 값&lt;/b&gt;을 가진다. 이 값은 말 그대로 남에게 얼마나 멋있는지의 정도, 즉 남(다른 스레드) 에게 얼마나 자신의 것(CPU time) 을 잘 양보하는지를 나타낸다. nice value 의 범위는 -20 부터 20의 값을 가진다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;nice value &amp;lt; 0 : 값이 작을수록 자신의 CPU time 을 다른 스레드에게 양보하는 정도가 크다.&lt;/li&gt;
&lt;li&gt;nice value = 0 : 스레드의 priority 에 영향을 끼치지 않는다.&lt;/li&gt;
&lt;li&gt;nice value &amp;gt; 0 : 값이 클수록 다른 스레드의 CPU time 을 더 많이 빼앗아 온다.&lt;/li&gt;
&lt;/ul&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;h3 data-ke-size=&quot;size23&quot;&gt;2. &lt;span style=&quot;background-color: #f3c000;&quot;&gt;Calculating Priority&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4.4BSD 스케줄러는 priority 를 0 (PRI_MIN) 부터 64 (PRI_MAX) 의 64개의 값으로 나눈다. 각 priority 별로 ready queue 가 존재하므로 64개의 ready queue 가 존재하며, priority 는 값이 커질수록 우선순위가 높아짐(먼저 실행됨)을 의미한다. 스레드의 priority 는 스레드 생성시에 초기화되고, &lt;b&gt;4 ticks&lt;/b&gt; 의 시간이 흐를때마다 모든 스레드의 priority 가 &lt;b&gt;재계산&lt;/b&gt;된다. priority 의 값을 계산하는 식은 아래와 같다.&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;priority = PRI_MAX - (recent_cpu / 4) - (nice * 2)&lt;/b&gt;&lt;/li&gt;
&lt;/ul&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;recent_cpu&lt;/b&gt; 값은 이 스레드가 &lt;b&gt;최근에 cpu 를 얼마나 사용하였는지&lt;/b&gt;를 나타내는 값으로, 최근에 사용한 cpu 양이 많을수록 큰 값을 가진다. 이는 오래된 thread 일수록 우선순위를 높게 가져서(recent_cpu 값이 작아짐) 모든 스레드들이 골고루 실행될 수 있게 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* priority 는 정수값을 가져야 하므로 계산 시 소수점은 버림한다.&lt;/p&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;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. &lt;span style=&quot;background-color: #f3c000;&quot;&gt;Calculating recent_cpu&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 priority 를 계산하는 데 사용되는 recent_cpu 값을 어떻게 계산하는지 알아보자. 각 스레드는 작업 도중에 여러 번 cpu 를 할당받아 작업을 수행하고 이 각각의 cpu 사용은 모두 recent_cpu 값에 영향을 주고 이 값이 클수록 priority 는 낮아진다. 여기서 중요한 점은 더 최근에 사용된 cpu 작업일수록 '&lt;b&gt;가중치&lt;/b&gt;' 가 붙는다는 것이다. 예를 들어서 thread A 가 1초가 걸리는 cpu 작업을 5번 연속으로 시행했다고 하면, recent_cpu 값은 가장 최근 작업은 1초를 그대로 반영하고, 바로 전 작업은 0.8, 그 전 작업은 0.64 ... 의 방식으로 일정 비율로 그 반영 비율을 달리한 모든 값의 합으로 결정된다. (recent_cpu = 1 + 0.8 + 0.64 + ..). 이러한 계산식을 '&lt;b&gt;지수가중 이동평균 방식(Exponentially Weighted Moving Average)&lt;/b&gt;' 라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;* 지수 가중 이동평균(Exponentially Weighted Moving Average)&lt;/h4&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;widthContent&quot; data-origin-width=&quot;176&quot; data-origin-height=&quot;17&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LWhkK/btq1V2XIQ2Q/f8fzfWznoiKucCfzXuk2O1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LWhkK/btq1V2XIQ2Q/f8fzfWznoiKucCfzXuk2O1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LWhkK/btq1V2XIQ2Q/f8fzfWznoiKucCfzXuk2O1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLWhkK%2Fbtq1V2XIQ2Q%2Ff8fzfWznoiKucCfzXuk2O1%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;197&quot; height=&quot;17&quot; data-origin-width=&quot;176&quot; data-origin-height=&quot;17&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;x(t) 의 값은 t 번째 데이터의 지수 가중 이동평균을 나타내고, a 는 하이퍼 파라미터로 최적의 값을 대입해 사용하며, f(t) 는 t 번째 데이터의 값을 나타낸다. 우리의 주제에서 사용될때는 x(t) 는 t 시간에서의 recent_cpu 값, a 는 부패율, f(t) 는 t 시간에서 사용한 cpu 양 정도로 대입하면 될 듯하다.&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;widthContent&quot; data-origin-width=&quot;176&quot; data-origin-height=&quot;17&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LWhkK/btq1V2XIQ2Q/f8fzfWznoiKucCfzXuk2O1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LWhkK/btq1V2XIQ2Q/f8fzfWznoiKucCfzXuk2O1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LWhkK/btq1V2XIQ2Q/f8fzfWznoiKucCfzXuk2O1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLWhkK%2Fbtq1V2XIQ2Q%2Ff8fzfWznoiKucCfzXuk2O1%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;197&quot; height=&quot;17&quot; data-origin-width=&quot;176&quot; data-origin-height=&quot;17&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;222&quot; data-origin-height=&quot;17&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/X14iW/btq10LzYbw0/EaI0zoon9jkxkK4kvPOVgk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/X14iW/btq10LzYbw0/EaI0zoon9jkxkK4kvPOVgk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/X14iW/btq10LzYbw0/EaI0zoon9jkxkK4kvPOVgk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FX14iW%2Fbtq10LzYbw0%2FEaI0zoon9jkxkK4kvPOVgk%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;222&quot; height=&quot;17&quot; data-origin-width=&quot;222&quot; data-origin-height=&quot;17&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;222&quot; data-origin-height=&quot;17&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bleFJR/btq1TMnAoWb/uvFJpCHCDA1YkBJRby6Duk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bleFJR/btq1TMnAoWb/uvFJpCHCDA1YkBJRby6Duk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bleFJR/btq1TMnAoWb/uvFJpCHCDA1YkBJRby6Duk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbleFJR%2Fbtq1TMnAoWb%2FuvFJpCHCDA1YkBJRby6Duk%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;222&quot; height=&quot;17&quot; data-origin-width=&quot;222&quot; data-origin-height=&quot;17&quot;/&gt;&lt;/span&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;맨 위의 x(t) 에 대한 식은 x(t-1) 을 아래 식으로 대체해주면 아래와 같이 표현된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;320&quot; data-origin-height=&quot;19&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bY53B8/btq1V1xxoqg/2kD00WT5jp3l8Prx8suqx1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bY53B8/btq1V1xxoqg/2kD00WT5jp3l8Prx8suqx1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bY53B8/btq1V1xxoqg/2kD00WT5jp3l8Prx8suqx1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbY53B8%2Fbtq1V1xxoqg%2F2kD00WT5jp3l8Prx8suqx1%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;320&quot; height=&quot;19&quot; data-origin-width=&quot;320&quot; data-origin-height=&quot;19&quot;/&gt;&lt;/span&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;여기서 x(t-2) 를 다시 한 번 대체하여 풀어주어 보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;464&quot; data-origin-height=&quot;19&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/M7OWN/btq1XlB2mb5/jUOdjfykZk9mChzK5tUtRK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/M7OWN/btq1XlB2mb5/jUOdjfykZk9mChzK5tUtRK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/M7OWN/btq1XlB2mb5/jUOdjfykZk9mChzK5tUtRK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FM7OWN%2Fbtq1XlB2mb5%2FjUOdjfykZk9mChzK5tUtRK%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;464&quot; height=&quot;19&quot; data-origin-width=&quot;464&quot; data-origin-height=&quot;19&quot;/&gt;&lt;/span&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;902&quot; data-origin-height=&quot;21&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dJzNnH/btq1Wi678mB/x2zLnClBwewWbOPiVke091/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dJzNnH/btq1Wi678mB/x2zLnClBwewWbOPiVke091/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dJzNnH/btq1Wi678mB/x2zLnClBwewWbOPiVke091/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/dJzNnH/btq1Wi678mB/x2zLnClBwewWbOPiVke091/img.gif&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;902&quot; height=&quot;21&quot; data-origin-width=&quot;902&quot; data-origin-height=&quot;21&quot;/&gt;&lt;/span&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;하이퍼 파라미터인 a 값은 0 과 1 사이의 값으로 사용된다. 0~1 범위의 값은 제곱 할 수록 그 값이 작아지기 때문에, 위의 식의 우변의 항들은 &lt;b&gt;뒤로 갈수록 a 가 한 번씩 곱해져서 그 값의 가중치가 작아진다&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-a, 등비는 a 인 등비수열이기 때문에 등비수열의 합 공식을 이용하여 각 계수의 합을 구해볼 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;612&quot; data-origin-height=&quot;21&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d3aE98/btq1ZMlN4Ne/HM2LivUyJRGW5DhboKa61K/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d3aE98/btq1ZMlN4Ne/HM2LivUyJRGW5DhboKa61K/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d3aE98/btq1ZMlN4Ne/HM2LivUyJRGW5DhboKa61K/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/d3aE98/btq1ZMlN4Ne/HM2LivUyJRGW5DhboKa61K/img.gif&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;612&quot; height=&quot;21&quot; data-origin-width=&quot;612&quot; data-origin-height=&quot;21&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;692&quot; data-origin-height=&quot;21&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c0HfMS/btq1Xh7Vj4J/XvPyUjqkIwKj9iEmmZnFIk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c0HfMS/btq1Xh7Vj4J/XvPyUjqkIwKj9iEmmZnFIk/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c0HfMS/btq1Xh7Vj4J/XvPyUjqkIwKj9iEmmZnFIk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/c0HfMS/btq1Xh7Vj4J/XvPyUjqkIwKj9iEmmZnFIk/img.gif&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;692&quot; height=&quot;21&quot; data-origin-width=&quot;692&quot; data-origin-height=&quot;21&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;widthContent&quot; data-origin-width=&quot;252&quot; data-origin-height=&quot;17&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3yJUS/btq1ZLN0GcK/JQIDWC5v87A5faEnZBNO3k/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3yJUS/btq1ZLN0GcK/JQIDWC5v87A5faEnZBNO3k/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3yJUS/btq1ZLN0GcK/JQIDWC5v87A5faEnZBNO3k/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/3yJUS/btq1ZLN0GcK/JQIDWC5v87A5faEnZBNO3k/img.gif&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;252&quot; height=&quot;17&quot; data-origin-width=&quot;252&quot; data-origin-height=&quot;17&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;111&quot; data-origin-height=&quot;16&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzh8bR/btq1056dFVb/GZvsHZNQkKUNtXgVlE3rFk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzh8bR/btq1056dFVb/GZvsHZNQkKUNtXgVlE3rFk/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzh8bR/btq1056dFVb/GZvsHZNQkKUNtXgVlE3rFk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bzh8bR/btq1056dFVb/GZvsHZNQkKUNtXgVlE3rFk/img.gif&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;111&quot; height=&quot;16&quot; data-origin-width=&quot;111&quot; data-origin-height=&quot;16&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;a 의 값이 0 초과, 1 미만의 값이므로 이렇게 정리가 된다. 여기서 모든 항의 합을 구하고자 하였으므로 n 을 무한으로 보내면,&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;192&quot; data-origin-height=&quot;17&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xaLDu/btq1XU5s1y6/8CGvw8B7FpwtzoDXt5hmX0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xaLDu/btq1XU5s1y6/8CGvw8B7FpwtzoDXt5hmX0/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xaLDu/btq1XU5s1y6/8CGvw8B7FpwtzoDXt5hmX0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/xaLDu/btq1XU5s1y6/8CGvw8B7FpwtzoDXt5hmX0/img.gif&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;192&quot; height=&quot;17&quot; data-origin-width=&quot;192&quot; data-origin-height=&quot;17&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;모든 계수의 합이 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;x(t) 의 값을 계산할 때마다 모든 데이터 t 에 대한 항을 모두 더하기에는 그 계산이 너무 많아지므로 부패가 많이 진행되어 그 가중치가 일정 수준 이하로 작아진 데이터들은 무시한 근사값으로 계산되는 경우가 많다. 가중치가 &lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;1/&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;e (대략 0.37) 이하로 내려갔을 때를 기준으로 다음 항들은 무시한다고 가정해보자. a = 0.9 의 값을 가진다고 하면 a 의 10 거듭제곱의 값이 대략 0.35 로 1/e 와 같으므로 최근 10 개 이전의 데이터들은 그 중요성을 상실하고, a = 0.98 일 경우에는 a 의 50 제곱이 1/e 와 비슷해져서 그 이전 데이터는 무시된다. &lt;b&gt;이 갯수값은 근사적으로 1/(1-a) 의 값과 같다&lt;/b&gt;. (a = 0.9 일 경우 1/(1-0.9) = 10, a = 0.09 일 경우 1/(1-0.09) = 50). 기준이 1/e^2 이라면 {1/(1-a)}^2 값이다.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&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;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;Pintos Document 의 내용에서는 &lt;b&gt;a = k/(k+1)&lt;/b&gt; 의 값으로 주어졌으며, 이를 통해 x(t) 를 k 와 연관시킬 수 있다. 바로 위의 내용에서 a 의 거듭제곱이 1/e 의 값과 대략 같아지는 지점이 a 의 1/(1-a) 거듭제곱이라고 했다. a 에 k/(k+1) 을 대입하면 아래와 같다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;285&quot; data-origin-height=&quot;39&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bweHuJ/btq1ZL1IwIJ/y59XsX3GwvA5Hlxctc4MH0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bweHuJ/btq1ZL1IwIJ/y59XsX3GwvA5Hlxctc4MH0/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bweHuJ/btq1ZL1IwIJ/y59XsX3GwvA5Hlxctc4MH0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bweHuJ/btq1ZL1IwIJ/y59XsX3GwvA5Hlxctc4MH0/img.gif&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;285&quot; height=&quot;39&quot; data-origin-width=&quot;285&quot; data-origin-height=&quot;39&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 식은 대략 k 번의 시간이 흘렀을 때 그 계수가 1/e 이하로 내려감을 보여준다. 이는 t + k 의 시간에 f(t) 의 부패율이 대략 1/e 이 된다는 것과 같은 말이고, 마찬가지로 t + 2k 의 시간에 f(t) 의 부패율이 1/e^2 이하가 된다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;81&quot; data-origin-height=&quot;21&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bsZ1gt/btq1056qUjD/5aKSxi4zsvWrlJxp7mgbBK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bsZ1gt/btq1056qUjD/5aKSxi4zsvWrlJxp7mgbBK/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bsZ1gt/btq1056qUjD/5aKSxi4zsvWrlJxp7mgbBK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bsZ1gt/btq1056qUjD/5aKSxi4zsvWrlJxp7mgbBK/img.gif&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;81&quot; height=&quot;21&quot; data-origin-width=&quot;81&quot; data-origin-height=&quot;21&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;100&quot; data-origin-height=&quot;21&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4jIV7/btq1YjRWJk1/S31a5Bjw9RDwMoy3FLSRK0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4jIV7/btq1YjRWJk1/S31a5Bjw9RDwMoy3FLSRK0/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4jIV7/btq1YjRWJk1/S31a5Bjw9RDwMoy3FLSRK0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/4jIV7/btq1YjRWJk1/S31a5Bjw9RDwMoy3FLSRK0/img.gif&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;100&quot; height=&quot;21&quot; data-origin-width=&quot;100&quot; data-origin-height=&quot;21&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;106&quot; data-origin-height=&quot;17&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ru9H9/btq1WJKD889/zzLpoEFIKz276ozhvD8rB1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ru9H9/btq1WJKD889/zzLpoEFIKz276ozhvD8rB1/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ru9H9/btq1WJKD889/zzLpoEFIKz276ozhvD8rB1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/ru9H9/btq1WJKD889/zzLpoEFIKz276ozhvD8rB1/img.gif&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;106&quot; height=&quot;17&quot; data-origin-width=&quot;106&quot; data-origin-height=&quot;17&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 식을 통해 &lt;b&gt;f(t) 의 부패율이 w 가 되는데 걸리는 시간은 대략 loga(w)&lt;/b&gt; 임을 알 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;적용&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;'지수 가중 이동평균'의 식은 컴퓨터에서 사용할 때는 주로 아래와 같이 V 의 값을 반복하여 덮어씌우는 방식으로 구현한다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-filename=&quot;스크린샷 2021-04-07 오전 9.41.38.png&quot; data-origin-width=&quot;866&quot; data-origin-height=&quot;250&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/caPDcY/btq1WOyGKFG/Mjg3YUOj4Gke395gd9ZFK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/caPDcY/btq1WOyGKFG/Mjg3YUOj4Gke395gd9ZFK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/caPDcY/btq1WOyGKFG/Mjg3YUOj4Gke395gd9ZFK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcaPDcY%2Fbtq1WOyGKFG%2FMjg3YUOj4Gke395gd9ZFK0%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;475&quot; height=&quot;250&quot; data-filename=&quot;스크린샷 2021-04-07 오전 9.41.38.png&quot; data-origin-width=&quot;866&quot; data-origin-height=&quot;250&quot;/&gt;&lt;/span&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;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;위의 내용을 토대로 recent_cpu 에 적용해보도록 하자.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;recent_cpu 의 초기값은 init 스레드의 경우에는 '0' 이고, 그 외에는 부모 thread 의 recent_cpu 값을 물려받는다. recent_cpu 의 값은 timer interrupt 가 발생할 때마다 running thread 에서 1 씩 증가하고, 매 1 초마다 모든 therad 에서 재계산된다. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;pintos 에서 recent_cpu 는 아래와 같이 정의된다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&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;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;recent_cpu = (2 * load_avg) / (2 * load_avg + 1) * recent_cpu + nice&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(2*load_avg)/(2*load_avt + 1) 부분을 부패율 a 라고 하고 보면, 처음 생성된 스레드의 경우 위의 규칙에 따라 recent_cpu 의 값이 0, nice, a*nice + nice, a*a*nice + a*nice + nice, ... 로 전개되어 나가는 것을 볼 수 있고, 이는 위에서 공부한 '지수 가중 이동평균' 와 같음을 알 수 있다. 이에 따라 각 시간에서 recent_cpu 의 값을 알 수 있고, 특정 시간의 recent_cpu 값이 특정 부패율이 되는 시간 또한 알 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, load_avg 값이 1 일 때 현재 recent_cpu 값의 부패율이 0.1 이 되는 시간은 아래의 식에 따라 대략 6초가 걸린다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;121&quot; data-origin-height=&quot;21&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdwvC9/btq1ZFgH4GU/VSdZsXnC0JuxrVTVkyYlrk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdwvC9/btq1ZFgH4GU/VSdZsXnC0JuxrVTVkyYlrk/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdwvC9/btq1ZFgH4GU/VSdZsXnC0JuxrVTVkyYlrk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bdwvC9/btq1ZFgH4GU/VSdZsXnC0JuxrVTVkyYlrk/img.gif&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;121&quot; height=&quot;21&quot; data-origin-width=&quot;121&quot; data-origin-height=&quot;21&quot;/&gt;&lt;/span&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* nice 값이 음수인 경우에 recent_cpu 값이 음수가 될 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* recent_cpu 의 계산과정에서 recent_cpu 의 계수를 먼저 계산하도록 한다. 계수의 계산 전에 recent_cpu 를 곱하는 경우 overflow 가 발생할 수 있다.&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;4. &lt;span style=&quot;background-color: #f3c000;&quot;&gt;Calculating load_avg&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;load_avg 값은 최근 1 분동안 수행 가능한(ready to run) 스레드의 평균 개수를 나타내며 recent_cpu 값과 마찬가지로 '지수 가중 이동평균' 을 사용한다. priority, recent_cpu 값은 각 thread 별로 그 값을 가지는 반면 &lt;b&gt;load_avg 는 system-wide 값&lt;/b&gt;으로 시스템 내에서 동일한 값을 가진다. load_avg 의 값은 시스템이 부팅될 때 0 으로 초기화되고, 매 1 초마다 아래 식에 따라 업데이트된다.&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;load_avg = (59/60)load_avg + (1/60)*ready_threads&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ready_threads 값은 업데이트 시점에 ready(running + ready to run) 상태인 스레드의 수를 나타낸다.&lt;/p&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;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. &lt;span style=&quot;background-color: #f3c000;&quot;&gt;Fixed-Point Real Arithmetic (고정 소수점 연산)&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;고정 소수점 VS 부동 소수점 방식&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴퓨터 시스템에서 정수와 다르게 소수점을 포함하는 실수의 표현은 생각보다 훨씬 복잡하다. 그 이유는 컴퓨터가 모든 수를 2진수로 표현하기 때문인데, 이를 해결하기 위해 현재에는 고정 소수점(Fixed-Point) 방식과 부동 소수점(Floating-Point) 방식이 사용된다. &lt;b&gt;고정 소수점 방식&lt;/b&gt;은 말 그대로 &lt;b&gt;소수점의 위치가&lt;/b&gt; &lt;b&gt;고정되어 있다&lt;/b&gt;는 뜻이고, &lt;b&gt;부동 소수점 방식&lt;/b&gt;이란 움직이지 않는다는 '부동' 의 의미가 아니라 영어에서 볼 수 있듯이 'floating' 즉&lt;b&gt; 소수점이 고정되어 있지 않고&lt;/b&gt; &lt;b&gt;'떠다니다'&lt;/b&gt;(이동한다) 의 의미이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게 32bit 를 예로 살펴보면, 고정 소수점 방식에서는 32bit 가 부호 비트, 정수부, 소수부 의 3 부분으로 나누어지고 각각의 bit 수는 시스템에서 정해져있고 변하지 않는다. 다만 정수부와 소수부의 bit 수 개수는 시스템마다 다를 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[0 &lt;span style=&quot;color: #ee2323;&quot;&gt;00000000000000000&lt;/span&gt; &lt;span style=&quot;color: #006dd7;&quot;&gt;00000000000000&lt;/span&gt;]&lt;/p&gt;
&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;정수부 : 정수를 표현한다.&lt;/li&gt;
&lt;li&gt;소수부 : 소수점 이하 자릿수를 나타낸다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정수부는 일반적인 2진수 계산으로 앞의 bit 로 갈수록 2 씩 곱하여 계산하지만, 소수부는 반대로 2 씩 나누어 계산한다. 아래 표는 정수부 5 bit, 소수부 5 bit 를 예로 각 자리수가 나타내는 값을 표시한다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 10%; text-align: center;&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;0&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center;&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;0&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center;&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;1&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center;&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;0&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center;&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;0&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center;&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;0&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center;&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;1&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center;&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;0&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center;&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;1&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center;&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;0&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 10%; text-align: center;&quot;&gt;16&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center;&quot;&gt;8&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center;&quot;&gt;4&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center;&quot;&gt;0.5&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center;&quot;&gt;0.25&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center;&quot;&gt;0.125&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center;&quot;&gt;0.0625&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center;&quot;&gt;0.03125&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 표는 bit 가 1 인 자리의 값을 더하면 되므로, 4.3125 라는 값을 나타낸다.&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;size18&quot;&gt;&lt;b&gt;적용&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부동 소수점 방식은 표현할 수 있는 범위가 크지만, 계산 시간이 길고 복잡하여 kernel 을 느리게 만들 수 있기 때문에 Pintos 시스템은 부동 소수점 방식은 지원하지 않는다. recent_cpu, load_avg 의 값은 실수로 표현되므로 pintos 에서는 1 bit(부호), 17 bit(정수부), 14 bit(소수부) 를 사용하는 고정 소수점 방식으로 실수를 계산한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게 계산하는 방법은, 실제 사용하고자 하는 실수에 2^14 값을 곱하여 사용하면 고정 소수점 방식으로 표현할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 2.75 라는 값을 사용하고자 한다면 2.75 * 2^14 = 45056 의 값을 사용하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;45056 = [1011000000000000] = [0 &lt;span style=&quot;color: #ee2323;&quot;&gt;00000000000000010&lt;/span&gt; &lt;span style=&quot;color: #006dd7;&quot;&gt;11000000000000&lt;/span&gt;] = [+&amp;nbsp; , 2&amp;nbsp; , 0.75]&lt;/p&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;예를 들어 5 + 2.75 를 계산한다고 하자. 5 는 정수이므로 2 진수로 101 로 표현되지만, 2.75 는 위에서 봤듯이&amp;nbsp;&lt;span style=&quot;color: #333333;&quot;&gt;1011000000000000 이다. 이 경우에는 5 를 실수의 표현 방법으로 변환하여 더하기를 진행히주어야 한다. 즉 5 * 2^14 + 2.75 를 해주면 고정 소수점 방식으로 표현된 올바른 값을 얻을 수 있다. 여러 연산의 방법은 아래 그림으로 첨부한다. (x, y 는 실수, n 은 정수, f 는 2^14 의 값이다.)&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-filename=&quot;스크린샷 2021-04-07 오전 11.39.54.png&quot; data-origin-width=&quot;958&quot; data-origin-height=&quot;672&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/H6dAZ/btq1XifEb07/APHPzEwa3rR4wj0z1oet5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/H6dAZ/btq1XifEb07/APHPzEwa3rR4wj0z1oet5K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/H6dAZ/btq1XifEb07/APHPzEwa3rR4wj0z1oet5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FH6dAZ%2Fbtq1XifEb07%2FAPHPzEwa3rR4wj0z1oet5K%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;642&quot; height=&quot;672&quot; data-filename=&quot;스크린샷 2021-04-07 오전 11.39.54.png&quot; data-origin-width=&quot;958&quot; data-origin-height=&quot;672&quot;/&gt;&lt;/span&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;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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Reference )&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;letter-spacing: 0px;&quot;&gt;지수가중이동평균 : &lt;/span&gt;&lt;a style=&quot;letter-spacing: 0px;&quot; href=&quot;https://hansololee.github.io/optimization/exponentially_weighted_averages/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;hansololee.github.io/optimization/exponentially_weighted_averages/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;pintos document : &lt;a style=&quot;letter-spacing: 0px;&quot; href=&quot;https://web.stanford.edu/class/cs140/projects/pintos/pintos.pdf&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;web.stanford.edu/class/cs140/projects/pintos/pintos.pdf&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>프로젝트/Pintos</category>
      <category>fixed point</category>
      <category>fixed_point real arithmetic</category>
      <category>floating number</category>
      <category>Floating Point</category>
      <category>load_avg</category>
      <category>niceness</category>
      <category>Priority</category>
      <category>recent_cpu</category>
      <author>aa_rong_blog</author>
      <guid isPermaLink="true">https://poalim.tistory.com/25</guid>
      <comments>https://poalim.tistory.com/25#entry25comment</comments>
      <pubDate>Sat, 3 Apr 2021 13:57:52 +0900</pubDate>
    </item>
    <item>
      <title>[Pintos] 동기화(Synchronization) - Semaphores, Locks, Monitors, Optimization Barriers</title>
      <link>https://poalim.tistory.com/24</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;스레드(Thread)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쓰레드는 프로세스 내에서 실행되는 흐름의 단위를 말한다. 환경에 따라 하나의 프로세스 내에서 두 개 이상의 쓰레드가 실행될 수 있는데 이를 &lt;b&gt;멀티스레드(multithread)&lt;/b&gt; 라고 한다. 멀티 쓰레드는 멀티 프로세스와 여러 작업이 동시에 이루어 진다는 공통점이 있지만, 멀티 프로세스에서 각 프로세스는 독립적으로 실행되며 각 프로세스는 별개의 메모리 공간을 차지하는 반면 멀티 스레드는 프로세스 내의 &lt;b&gt;메모리를 공유&lt;/b&gt;하여 사용할 수 있다는 차이점이 존재하며 이러한 &lt;b&gt;자원을 공유하는 특성&lt;/b&gt; 때문에 pintos 의 스레드 설계는 스레드간의 &lt;b&gt;동기화(Synchronization)&lt;/b&gt; 에 신경을 써야한다. 이 포스트는 pintos 의 동기화에 사용될 수 있는 여러 방법들을 소개한다.&lt;/p&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;h3 data-ke-size=&quot;size23&quot;&gt;Disabling Interrupts&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커널의 스케쥴링 방식에 따라 커널 스레드가 CPU 를 사용하고 있을 때 다른 스레드에 의해 CPU 의 사용을 선점(preemption) 당할 수 있는지의 여부가 달라진다. Pintos 는 커널 스레드가 언제든 선점당할 수 있는 &lt;b&gt;Preemptible&lt;/b&gt; &lt;b&gt;kernel&lt;/b&gt; 이다.(Non-preemtible kernel 에서는 스레드가 CPU 의 사용권을 다른 스레드에게 이양한 후에야 다른 스레드가 그 CPU 를 사용할 수 있다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Interrupt 를 비활성화 하는 것만으로 synchronization 을 실현할 수 있다. thread preemption 은 timer interrupt 에 의해 일어나기 때문에 interrupt 자체를 막아버리는 것이다. 이 방법은 external interrupt handlers 를 동기화시키기 위해 주로 사용되는데, external interrupt 는 프로그램 내부에서 발생하는 interrupt 와 다르게 외부에서 발생하는 interrupt 로, sleep 이 불가하기 때문에 다른 동기화 도구가 사용될 수 없고 가장 우선적으로 처리되어야 하기 때문에 외부 인터럽트가 실행되는 동안은 다른 모든 interrupt 를 비활성화한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;lt;thread/interrupt.h&amp;gt;&lt;/b&gt; 에 interrupt 들을 활성/비활성 할 수 있는 Type, function 들이 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Semaphores&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멀티 스레드 프로그램에서는 여러 스레드가 메모리 등의 자원을 공유하게 되고 여러 스레드에 의해 공유되어 사용되는 자원을 &lt;b&gt;공유자원&lt;/b&gt;이라고 한다. 각 스레드에서 이 공유자원을 엑세스 하는 코드구역을 &lt;b&gt;임계구역(Critical Section)&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;세마포(Semaphores) 는 공유자원을 여러 스레드가 동시에 접근하지 못하도록 하는 도구로 하나의 nonnegative integer 와 두 개의 operators 로 이루어진다. &lt;b&gt;nonnegative integer&lt;/b&gt;&amp;nbsp;는 &lt;b&gt;사용 가능한&lt;/b&gt;&amp;nbsp;&lt;b&gt;공유자원의 개수&lt;/b&gt;를 의미한다. 이 값이 2 라면 2개의 스레드가 동시에 접근 가능하고, 이 값이 0 이라면 사용 가능한 공유자원이 없음을 의미한다. 이 수는 다른 방식으로는 조작할 수 없고 오직 아래 두 개의 operator 로만 조작이 가능하다.&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;&quot;Down&quot;(or &quot;P&quot;)&lt;/b&gt; : 어떤 스레드가 임계구역으로 들어와서 공유자원을 사용하고자 요청할 때 실행되고, 현재 사용 가능한 공유자원의 개수가 1개 이상이면(양수개이면) 이 수를 1 줄이고 임계구역을 실행한다.(공유자원을 사용한다.) 만약 현재 사용 가능한 공유자원의 개수가 0 이하면 이 값이 양수가 될 때까지 임계구역을 실행하지 않고 기다린다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&quot;Up&quot;(or &quot;V&quot;)&lt;/b&gt; : 스레드가 임계구역의 실행을 모두 마치고 공유자원을 반납할 때 실행된다. 사용 가능한 공유자원의 개수를 1 늘린다.&lt;/li&gt;
&lt;/ul&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;특정 스레드가 단 한번 일어나는 다른 event 가 끝나길 기다려야 하는 경우 0 으로 초기화&lt;/b&gt; 된 세마포를 사용한다. 예를 들어, 스레드 A 가 진행 중에 스레드 B 를 시작하고 A 는 B 가 특정 작업을 끝내길 기다려야 하는 경우가 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-filename=&quot;스크린샷 2021-04-02 오전 10.00.51.png&quot; data-origin-width=&quot;792&quot; data-origin-height=&quot;538&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NGHSG/btq1C1SbumO/DuoYK3guYW7yV6NXW3BRC0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NGHSG/btq1C1SbumO/DuoYK3guYW7yV6NXW3BRC0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NGHSG/btq1C1SbumO/DuoYK3guYW7yV6NXW3BRC0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNGHSG%2Fbtq1C1SbumO%2FDuoYK3guYW7yV6NXW3BRC0%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;546&quot; height=&quot;538&quot; data-filename=&quot;스크린샷 2021-04-02 오전 10.00.51.png&quot; data-origin-width=&quot;792&quot; data-origin-height=&quot;538&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스레드 A 에서는 0 으로 초기화 한 세마포 S 를 만든 후 스레드 B 를 시작하면서 이 세마포를 함께 넘겨준 후 Down(S) 을 한다. 이 때, 현재 S.value 값이 0 이므로 스레드 A 는 block() 되어 기다리고, 스레드 B 는 특정 Event 를 수행한다. 이후 B 가 Event 를 끝내고 Up(S) 을 호출하면 S.value 값이 1이 되어 스레드 A 의 block() 이 풀리고, A 의 Down 으로 인해 바로 다시 S.value 값은 0 으로 되고 A 의 작업을 이어간다. 이 경우 스레드 A 의 start B 후 Down(S) 가 실행되기 전에 B 의 작업이 모두 끝나고 Up(S) 이 먼저 실행될 수 있지만, Down 에서 A 가 block 되지 않고 바로 진행된다는 점만 다를 뿐 세마포는 의도한 대로 작동한다.&lt;/p&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;하나의 공유자원에 접근을 제어할 때는 1 로 초기화된 세마포를 사용한다. &lt;/b&gt;스레드 A 가&amp;nbsp;공유자원을 사용하기 전에 &quot;Down&quot; 을 하면 A 가 우선 실행되고 도중에 공유자원에 접근하는 다른 스레드들은 A 가 &quot;Up&quot; 을 호출하기 전까지 대기한다.&lt;/p&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;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;세마포의 값이 0 으로 초기화 된 경우와 1 로 초기화 되는 경우가 헷갈리는 경우 다음과 같이 생각하면 될 것 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;0 으로 초기화 되는 경우 : 스레드 A 가 우선 기다림 -&amp;gt; 스레드 B 의 작업이 끝나면 A 가 실행&lt;/b&gt;&lt;/li&gt;
&lt;li data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1 로 초기화 되는 경우 : 스레드 A 가 우선 실행됨 -&amp;gt; A 의 작업이 끝나면 기다리던 B 가 실행&amp;nbsp;&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;세마포의 구조는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;&amp;lt;threads/synch.h&amp;gt;&lt;/b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;에 구현되어 있다. 세마포는 공유자원이 반환되기를 기다리며 block 되어 있는 waiting threads 의 list 를&amp;nbsp;&lt;/span&gt;&lt;b&gt;&amp;lt;lib/kernel/list.c&amp;gt;&lt;/b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;에 구현되어 있는&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;linked list 를 사용하여 저장한다. 아래는 pintos 에 구현되어 있는 세마포의 코드를 간단히 나타낸 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1617326417457&quot; class=&quot;c++ arduino&quot; style=&quot;display: block; overflow: auto; padding: 15px; color: #383a42; background: #f6f7f8; font-size: 14px; border-radius: 3px; font-family: Menlo, Consolas, Monaco, monospace; border: 1px solid #dddddd; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void down(S){
    if(S.value == 0){
    	add this thread to S.list
        block();
    }
    S.value--;
}

void up(S){
    if(S.list is not empty){
    	remove this thread to S.list
        wakeUp(P)
    }
    S.value++;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Locks&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;lock 도 마찬가지로 공유자원을 관리하는 동기화도구 중 하나로, 위에서 설명한 초기화 값이 1 인 세마포와 거의 비슷하게 동작한다. lock 에서는 &quot;Up&quot; 대신 &lt;b&gt;&quot;release&quot;&lt;/b&gt;, &quot;Down&quot; 대신 &lt;b&gt;&quot;acquire&quot;&lt;/b&gt; 라는 이름의 operator 로 동작한다. lock 에는 세마포에는 없는 하나의 제약이 있는데, &lt;b&gt;acquire 를 호출한 스레드만이 해당 lock 을 release 할 수 있다&lt;/b&gt;는 것이다. 0 으로 초기화 된 세마포의 설명에서 세마포는 스레드 A 에서 Down 을 하고 B 에서 Up 이 가능하였지만 lock 은 이러한 동작이 불가능하다. 이러한 제약으로 인해 문제가 발생한다면, 세마포를 사용하도록 하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Lock 의 구조 및 코드 역시 &lt;b&gt;&amp;lt;threads/synch.h&amp;gt;&lt;/b&gt; 에 구현되어 있다.&lt;/p&gt;

&lt;h3 data-ke-size=&quot;size23&quot;&gt;Monitors&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모니터는 동기화 문제를 해결하는 세마포나 락보다 활용도가 높고 사용이 쉬운 도구이다. 모니터는 공유자원, 모니터 락, 조건변수로 이루어진다. 모니터 락은 위에서 설명한 락과 유사하게 동작한다. lock 을 acquire 하면 이 스레드는 release 되기 전까지&amp;nbsp;&lt;b&gt;&quot;in the monitor&quot;&lt;/b&gt; 상태가 된다. 이 상태에 여러 개의 스레드들이 있을 수 있고, 여기에 있는 스레드들은 서로 &lt;b&gt;Mutual Exclusion(상호배타)&lt;/b&gt;, 즉 공유자원을 하나의 스레드만 사용할 수 있는 상태가 된다. 별다른 조건이 없다면 여기에 있는 스레드들은 하나씩 차례로 실행되고 release 를 통해 빠져나간다. 모니터에는 여기에 추가로 &lt;b&gt;Condition variables(조건 변수)&lt;/b&gt;를 추가할 수 있다. 임계구역을 실행중이던 스레드는 &lt;b&gt;wait(condition)&lt;/b&gt; 명령을 통해 실행을 멈추고 대기 상태로 들어갈 수 있고, 이후 다른 대기중이던 스레드가 임계구역을 실행시킨다. 대기 상태의 스레드는 condition 이 true 가 될 때까지 대기하게 되는데, 실행중인 스레드에서 &lt;b&gt;signal&lt;/b&gt;&lt;b&gt;(condition)&lt;/b&gt; 명령을 통해 wait 되어 있는 스레드 중 하나의 condition 을 true 로 만들어 깨울 수 있다. &lt;b&gt;broadcast(condition)&amp;nbsp;&lt;/b&gt;명령어는 현재 wait 중인 모든 condition 을 모두 true 로 만들어 깨운다.&lt;/p&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 id=&quot;code_1617333034615&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void put (char ch) {
    lock_acquire (&amp;amp;lock);
    while (n == BUF_SIZE)
    	cond_wait (&amp;amp;not_full, &amp;amp;lock);
    buf[head++ % BUF_SIZE] = ch;
    n++;
    cond_signal (&amp;amp;not_empty, &amp;amp;lock);
    lock_release (&amp;amp;lock);
}

char get (void) {
    char ch;
    lock_acquire (&amp;amp;lock);
    while (n == 0)
    	cond_wait (&amp;amp;not_empty, &amp;amp;lock);
    ch = buf[tail++ % BUF_SIZE];
    n--;
    cond_signal (&amp;amp;not_full, &amp;amp;lock);
    lock_release (&amp;amp;lock);
}&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;이 예제에서 producer 스레드들은 put 함수를 통해 글자를 입력하고, consumer 스레드들은 get 함수를 통하여 글자들을 읽어온다. 버퍼의 크기는 한정적이므로 BUF_SIZE 가 가득 찬 경우에는 put 을 block 하여야 하고, 더 이상 읽어 올 글자가 버퍼에 남아 있지 않은 경우에는 get 을 block 하여야 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-filename=&quot;스크린샷 2021-04-02 오후 12.27.47.png&quot; data-origin-width=&quot;902&quot; data-origin-height=&quot;856&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xziWh/btq1D1qVH4g/CQVoSBRPWD1CS2oLuratc1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xziWh/btq1D1qVH4g/CQVoSBRPWD1CS2oLuratc1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xziWh/btq1D1qVH4g/CQVoSBRPWD1CS2oLuratc1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxziWh%2Fbtq1D1qVH4g%2FCQVoSBRPWD1CS2oLuratc1%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;490&quot; height=&quot;856&quot; data-filename=&quot;스크린샷 2021-04-02 오후 12.27.47.png&quot; data-origin-width=&quot;902&quot; data-origin-height=&quot;856&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 버퍼에 아무 글자도 없을 것이기 때문에 get 은 cond_wait(&amp;amp;not_empty) 에 의해 not_empty 가 true 가 될 때까지 대기 상태가 된다. 이와 동시에 put 함수에서는 하나의 글자를 버퍼에 쓰고 cond_signal(&amp;amp;not_empty) 를 하여 &amp;amp;not_empty 라는 조건을 true 로 만들어주고, 이는 get 함수가 대기상태에서 깨어나 다시 진행되게 만들며 버퍼에 쓰여진 글자가 있기 때문에 정상적으로 글자를 읽어낸다. 반대의 경우도 마찬가지로 put 함수가 반복되어 버퍼가 가득 차게 되면 put 함수에서는 cond_wait(&amp;amp;not_full) 로 put 함수가 더 이상 실행될 수 없도록 대기 상태로 만들고 get 함수가 글자를 읽어 버퍼를 비워준 후에 cond_signal(&amp;amp;not_full) 을 통해 not_full 조건을 true 로 만들어 준 후에 다시 실행된다.&lt;/p&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;h3 data-ke-size=&quot;size23&quot;&gt;Optimization Barriers&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴파일러는 프로그램의 최적화를 위해(성능향상을 위해) 처리 순서에 dependency 가 없는 경우 statements 를 임의로 reorder 하는 경우가 있다. 하지만 얼핏 연관(dependency) 가 없어보이지만 실제로는 연관성이 있는 경우가 있다. 이러한 경우 컴파일러가 임의로 순서를 바꾸거나 최적화 해버리는 실수를 방지하기 위해 사용되는 것이 &lt;b&gt;Optimization Barriers&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;pre id=&quot;code_1617335517781&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* &amp;lt;devices/timer.c&amp;gt;
 * Wait for a timer tick. */
int64_t start = ticks;
while (ticks == start)
    barrier ();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것은 too_many_loops() 라는 함수에 존재하는 코드인데, while 문을 통해 반복문을 여러차례 반복시켜서 ticks 가 흘러가게 하여 시간을 재는 용도로 이용된다. 하지만 이러한 배경이 없이 이 코드 자체만 보면, barrier() 라는 부분이 없다면 while(ticks == start) 부분은 내부에서 아무런 작업도 하지 않으므로 컴파일러가 자칫 무한루프라고 인식하여 코드 자체를 무한루프로 최적화시킬 수 있다. barrier() 는 이러한 최적화를 하지 않도록 컴파일러에게 알려주는 명령이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멀티스레드 간의 실행에서 barrier() 사용의 예제를 하나 더 보도록 하자.&lt;/p&gt;
&lt;pre id=&quot;code_1617335910517&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* thread 1 */
x = 0;
while (x == 0)
    barrier();
print y;


/* thread 2 */
y = 1;
barrier();
y = 10;
barrier();
x = 1;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드의 의도는 thread 1 의 print y 에서 y 의 값으로 10이 출력되게 하고 싶다. barrier() 가 없다면 컴파일러가 실수할 수 있는 상황이 몇 가지 있다. 우선, thread 1 에서 컴파일러는 while (x == 0) 를 무한루프로 인식하고 더 이상 진행하지 않도록 최적화할 수가 있다. 이 경우 y 는 출력되지 않는다. 또한, thread 2 에서 y = 1, y = 10 은 그 자체로 보면 순서가 상관없는 코드이므로 컴파일 단계에서 순서가 뒤바뀔 가능성이 있다. 이 경우 y 는 1 로 출력된다. 마찬가지로 x = 1 또한 y = 1, y = 10 과 순서가 뒤바뀔 수 있는데, 이 경우 x = 1 이 먼저 실행되면 y = 10 이 실행되기 전에 thread 1 의 print y 가 실행되어 의도하지 않은 y 값이 출력되는 상황이 발생할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프로젝트/Pintos</category>
      <category>Locks</category>
      <category>monitors</category>
      <category>Operating System</category>
      <category>Optimization Barriers</category>
      <category>os</category>
      <category>pintos</category>
      <category>Semaphores</category>
      <author>aa_rong_blog</author>
      <guid isPermaLink="true">https://poalim.tistory.com/24</guid>
      <comments>https://poalim.tistory.com/24#entry24comment</comments>
      <pubDate>Wed, 31 Mar 2021 11:37:17 +0900</pubDate>
    </item>
    <item>
      <title>[Pintos] 로딩 (Loading) - Loader, Real mode, Protected mode, A20 gate, Page Table</title>
      <link>https://poalim.tistory.com/23</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;로딩&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로딩은 Pintos 의 Loader 의 실행과 kernel initialization 작업을 포함한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #f3c000;&quot;&gt;1. Loader&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;여기서 로딩은 pintos 의 첫 실행시 진행되는 부팅의 과정을 나타낸다. 부팅이란 컴퓨터가 구동하여 기초적인 초기화 작업을 수행하고 운영 체제를 읽어오는 일련의 작업을 말한다. 로더(Loader) 는 이러한 부팅의 작업을 진행하는 프로그램으로 장치(Disk) 의 첫 번째 트랙의 첫 번째 섹터에 저장되어 있다.&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;Pintos 에서 로딩의 대략적인 순서는 아래와 같다. (로더의 코드는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;&amp;lt;threads/loader.S&amp;gt;&lt;/b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;에 구현되어 있다.)&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;1. BIOS 가 로더(Loader) 를 메모리에 로드(Load) 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;BIOS 는 컴퓨터가 구동된 후 저장 장치의 &lt;b&gt;첫 번째 섹터(Sector)&lt;/b&gt; 를 탐색하여 이 장치가 부팅 가능한지 검사한다.
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;Sector : 저장 장치(Disk) 에서 데이터를 저장하는 최소 단위로, 한 섹터의 크기는 512 bytes 이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;이 첫 번째 섹터를 &lt;b&gt;MBR&lt;/b&gt;(Master Boot Record) 라고 하고, 부팅 가능한 장치임을 나타내기 위해서는 섹터의 마지막 2 bytes 가 0xAA55 (Magic Number) 의 값을 가져야 한다. 이러한 검사 과정은 아래와 같이 구현되어 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-filename=&quot;스크린샷 2021-03-30 오후 3.05.12.png&quot; data-origin-width=&quot;852&quot; data-origin-height=&quot;110&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bX441V/btq1qOYYIEn/KSCEKldeCG8JbWKRyycc7k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bX441V/btq1qOYYIEn/KSCEKldeCG8JbWKRyycc7k/img.png&quot; data-alt=&quot;&amp;amp;lt;thread/loader.S&amp;amp;gt;, line 261&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bX441V/btq1qOYYIEn/KSCEKldeCG8JbWKRyycc7k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbX441V%2Fbtq1qOYYIEn%2FKSCEKldeCG8JbWKRyycc7k%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;511&quot; height=&quot;110&quot; data-filename=&quot;스크린샷 2021-03-30 오후 3.05.12.png&quot; data-origin-width=&quot;852&quot; data-origin-height=&quot;110&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;&amp;lt;thread/loader.S&amp;gt;, line 261&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;부팅 가능한 장치임이 판명되면 BIOS 는 이 첫번재 섹터를 읽어서 메모리의 &lt;b&gt;0x7c00&amp;nbsp;&lt;/b&gt;위치에 올려놓고 작업을 시작한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 로더가 디스크에서 커널(Kernel) 을 찾아 메모리의 &lt;b&gt;0x20000 &lt;/b&gt;위치에&amp;nbsp;로드한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 커널의 entry point 로 이동한다. (kernel 의 ELF header 가 entry point 의 위치를 포함하고 있다.)&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;span style=&quot;background-color: #f3c000;&quot;&gt;2. Low-Level Kernel Initialization&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로더는 control 을 kernel 의 entry point 로 옮긴다.&amp;nbsp; 이것은 start 라는 위치로 표현되어 있으며, &lt;b&gt;&amp;lt;thread/start.S&amp;gt;&lt;/b&gt; 에 구현되어 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-filename=&quot;스크린샷 2021-03-27 오후 1.35.07.png&quot; data-origin-width=&quot;214&quot; data-origin-height=&quot;42&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GEyn2/btq09i1h8Ez/XaptL8AySpCRx3XCOi9cM1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GEyn2/btq09i1h8Ez/XaptL8AySpCRx3XCOi9cM1/img.png&quot; data-alt=&quot;&amp;amp;lt;thread/loader.S&amp;amp;gt; line 168&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GEyn2/btq09i1h8Ez/XaptL8AySpCRx3XCOi9cM1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGEyn2%2Fbtq09i1h8Ez%2FXaptL8AySpCRx3XCOi9cM1%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;214&quot; height=&quot;42&quot; data-filename=&quot;스크린샷 2021-03-27 오후 1.35.07.png&quot; data-origin-width=&quot;214&quot; data-origin-height=&quot;42&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;&amp;lt;thread/loader.S&amp;gt; line 168&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;start() 코드의 역할은 CPU 를 16-bit 의 리얼모드(real mode) 에서 32-bit 보호모드(protected mode) 로 바꿔주고 main() 함수를 부르는 역할을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Real mode&amp;nbsp; &amp;nbsp; &amp;nbsp;vs&amp;nbsp; &amp;nbsp; Protected mode&amp;nbsp; ?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;real mode&lt;/b&gt; 란 컴퓨터가 기동되고 바로 동작하는 모드로 loader 의 코드는 real mode 에서 동작한다. 특징은 프로그램이 동작할 때 physical memory 에 직접 프로그램이 통째로 올라간다. 이러한 특성상 여러가지 단점이 존재한다. 첫 번째, physical memory 에는 한 번에 한 개의 프로그램만 적재 가능하기 때문에 &lt;b&gt;한 번에 한 개의 프로그램밖에 실행되지 못한다.&amp;nbsp;&lt;/b&gt;두 번째, physical memory 에 올라간 프로그램이 종료되지 않으면 &lt;b&gt;인터럽트나 시스템 종료를 통하지 않고는 다른 프로그램의 실행이 불가능하다.&amp;nbsp;&lt;/b&gt;마지막으로 physical memory 에 직접 접근하므로 응용 프로그램에서 시스템 코드로의 접근이 용이하고 이는 &lt;b&gt;보안에 취약하고 시스템 손상을 유발한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이러한 이유로 pintos 를 포함하여 최근 대부분의 OS 는 보호모드로 동작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;protected mode&amp;nbsp;&lt;/b&gt;란 각 프로세스마다 보조기억장치에 각자의 가상 메모리(Virtual memory) 영역을 할당받는다(32bit system 의 경우 각 4GB). 이 영역은 각자가 real mode 인 것 처럼 동작하고 페이징 기법(Paging) 을 통하여 &lt;b&gt;실제 필요한 부분만 physical memory 에 올라와서 실행&lt;/b&gt;되기 때문에 real mode 의 단점들을 보완한다. 실제 필요한 부분만 physical memory 에 올라와서 실행되기 때문에 여러 프로그램을 동시에 실행이 가능하고 응용 프로그램에서 시스템 코드로의 접근을 제한해 시스템 손상을 막는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;start() 코드의 전체적인 실행 과정은 아래와 같다.&lt;/b&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp; 1. PC's memory size 를 얻어 init_ram_pages (in &amp;lt;threads/loader.h&amp;gt;)에 저장한다.&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Memory size 를 얻기 위해서 interrupt를 사용하며, interrupt 번호는 0x15, 함수번호는 0x88 번이다.&lt;/li&gt;
&lt;li&gt;인터럽트 함수를 통해 반환되는 값은 (Physical Memory 의 실제 크기, 단위 KB) - 1024KB 이고, 로더는 여기에 1024KB 를 더해 실제 Physical Memory 의 크기를 얻는다.&lt;/li&gt;
&lt;li&gt;아직 &lt;b&gt;16-bit 의 리얼모드(real mode)&lt;/b&gt; 에서 동작하므로 BIOS 는 최대 &lt;b&gt;64MB&lt;/b&gt;(2^16 KB) 만을 탐색할 수 있기 때문에 &lt;b&gt;pintos 에서 사용하는 Physical Memory 의 크기는 최대 64MB 로 제한된다.&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp; 2. A20 line 을 활성화한다. (CPU's address line numbered 20)&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;A20 line 이란?&lt;/b&gt;&lt;/li&gt;
&lt;li data-ke-size=&quot;size18&quot;&gt;x86 기반의 CPU 의 numbered 20 address bus line 에 있는 and gate 를 말한다.&lt;/li&gt;
&lt;li data-ke-size=&quot;size18&quot;&gt;20bit 에서 32bit 아키텍처로 넘어가는 기점에서 원래의 20bit 를 기반으로 구현된 프로그램을 실행할 때 아키텍처 차이로 인한 오버플로우를 막고 하위호환을 위하여 20번째 address bus line 에 and gate 를 달아서 20bit 아키텍처에서와 같은 동작을 하도록 만든 gate 이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;962&quot; data-origin-height=&quot;512&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ceuYA2/btq0816EqIg/whyBy1JXtpcZgDGDkPCUzK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ceuYA2/btq0816EqIg/whyBy1JXtpcZgDGDkPCUzK/img.png&quot; data-alt=&quot;이미지 출처 - https://b.luavis.kr/os/a20&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ceuYA2/btq0816EqIg/whyBy1JXtpcZgDGDkPCUzK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FceuYA2%2Fbtq0816EqIg%2FwhyBy1JXtpcZgDGDkPCUzK%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;650&quot; height=&quot;512&quot; data-origin-width=&quot;962&quot; data-origin-height=&quot;512&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이미지 출처 - https://b.luavis.kr/os/a20&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ex. A20 line 이 0 으로 초기화 되어 있는 상태에서 0x100000-0x1fffff, 0x300000-0x3fffff 는 각각 0x000000-0x0fffff, 0x200000-0x2fffff 로 매핑됨.&lt;/li&gt;
&lt;li data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;pintos 에서 1MB(2^20) 이상의 메모리에 접근하기 위하여 A20 line 을 1로 활성화 시켜 주어야 한다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp; 3. 메모리 초기화&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;Page Table 이란? &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;페이지(Page) 와 프레임(Frame)&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;OS 는 physical memory 와 virtual memory 의 관리를 용이하게 하기 위해 각 메모리를 같은 크기를 가진 단위 블록으로 나누어 관리한다. physical memory 를 구성하는 단위 블록을 &lt;b&gt;Frame&lt;/b&gt;, virtual memory 를 구성하는 단위 블록은 &lt;b&gt;Page&lt;/b&gt; 라고 한다. virtual memory 에 있는 page 들은 실제 실행될 때 physical memory 의 frame 을 할당받아 physical memory 에 위치하게 된다.(frame과 page 는 같은 크기로 관리되며 x86 아키텍처에서 하나의 page, frame 의 크기는 &lt;b&gt;4KB&lt;/b&gt; 이다.)&lt;/li&gt;
&lt;li&gt;Page 들을 관리하기 위한 테이블을 &lt;b&gt;Page Table&lt;/b&gt; 이라고 하고, 이 Page Table 은 하나의 프로세스마다 하나씩 존재한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Page directory&lt;/b&gt; 란 &lt;b&gt;Page Directory Entry(PDE)&lt;/b&gt; 들로 구성된 데이터이다. 각 PDE 는 하나의 Page Table 의 첫 주소, 즉 Page Table 의 포인터를 정보로 가지고 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Page table&lt;/b&gt; 도 마찬가지로 &lt;b&gt;Page Table Entry(PTE)&lt;/b&gt; 들로 구성된다. 각 PTE 는 하나의 Page 의 첫 주소, 즉 Page 의 포인터를 정보로 가진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;430&quot; data-origin-height=&quot;209&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZIEyk/btq09jlHXLG/3vhIWRstOxaDEjg0uy0lZ1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZIEyk/btq09jlHXLG/3vhIWRstOxaDEjg0uy0lZ1/img.jpg&quot; data-alt=&quot;이미지 출처 -&amp;amp;amp;nbsp;https://www.sciencedirect.com/topics/computer-science/page-directory-table&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZIEyk/btq09jlHXLG/3vhIWRstOxaDEjg0uy0lZ1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZIEyk%2Fbtq09jlHXLG%2F3vhIWRstOxaDEjg0uy0lZ1%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;430&quot; height=&quot;209&quot; data-origin-width=&quot;430&quot; data-origin-height=&quot;209&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이미지 출처 -&amp;amp;nbsp;https://www.sciencedirect.com/topics/computer-science/page-directory-table&lt;/figcaption&gt;
&lt;/figure&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;Pintos 의 Table 구조&lt;/b&gt;&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Pintos 는 &lt;b&gt;4KB&lt;/b&gt; 크기의 &lt;b&gt;Page directory&lt;/b&gt; 를 하나 가지고 있다.&lt;/li&gt;
&lt;li&gt;32bit 아키텍처에서 하나의 주소 값은 4B(32bit) 를 차지하므로 4KB 의 Page directory 는 &lt;b&gt;1024&lt;/b&gt;(4KB/4B = 1K = 1024)개의 &lt;b&gt;PDE&lt;/b&gt; 를 가진다.&lt;/li&gt;
&lt;li&gt;하나의 Page table 은 4KB 의 크기를 가지고, Page directory 와 마찬가지로&amp;nbsp;&lt;b&gt;1024&lt;/b&gt;개의 &lt;b&gt;PTE&lt;/b&gt; 를 가진다.&lt;/li&gt;
&lt;li&gt;각 Page 는 4KB 의 크기를 가진다.&lt;/li&gt;
&lt;li&gt;이를 통해 pintos 가 32bit 의 Linear address 로 표현할 수 있는 &lt;b&gt;Virtual Memory 의 크기&lt;/b&gt;는 1024(PDE) * 1024(PTE) * 4KB(Page) = 2^20 * 4KB = 1MB * 4KB = &lt;b&gt;4GB&lt;/b&gt; 라는 것을 알 수 있다. 즉, pintos 의 각 process 는 4GB 의 virtual memory 공간을 갖는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Basic Page Table Mapping&lt;/b&gt;&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;보호모드&lt;/b&gt;로 전환된 후의 접근가능한 Virtual Memory 의 크기는 &lt;b&gt;4GB&lt;/b&gt; 이고, pintos 는 이 중에서 &lt;b&gt;0xc0000000-0xffffffff(3GB-4GB) 의 1GB 공간을 커널 영역으로 사용&lt;/b&gt;한다. (이 커널의 시작 위치는 &lt;b&gt;LOADER_PHYS_BASE&lt;/b&gt;(0xc0000000) 라는 값으로 &amp;lt;thread/loader.h&amp;gt; 에 정의되어 있다.)&lt;/li&gt;
&lt;li&gt;기본적인 Page Table 을 만들어, 현재 Physical Memory address 를 Virtual Memory 의 0-64MB 에 그대로 mapping한다.&lt;br /&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;기본적인 Page Table 을 구성하기 위한 과정이다. 리얼모드에서는 64MB 이상의 Virtual address 에 접근이 불가능하기 때문에 이 과정이 우선되어야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;동일한 Physical Memory address 를 Virtual Memory 의 &lt;b&gt;LOADER_PHYS_BASE&lt;/b&gt; 에 mapping 한다.&lt;br /&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;보호모드로 바뀐 후에는 Physical Memory 의 0-64MB 의 공간에 직접 접근해서 사용하는 것이 불가하므로 LOADER_PHYS_BASE 를 더한 커널 영역의 Virtual address 에 접근하여 사용하게 하기 위함이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp; 4. 모드를 리얼모드에서 보호모드로 변경한 후 main() 함수를 call한다.&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; &amp;nbsp; * 부팅 과정에서 Physical Memory 에 올라오는 값들은 아래 그림에 자세히 설명되어 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-filename=&quot;스크린샷 2021-03-19 오후 1.06.30.png&quot; data-origin-width=&quot;586&quot; data-origin-height=&quot;372&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ekPTcv/btq0tXwhAXa/MalIJENxs3TAgGKjOlep2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ekPTcv/btq0tXwhAXa/MalIJENxs3TAgGKjOlep2k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ekPTcv/btq0tXwhAXa/MalIJENxs3TAgGKjOlep2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FekPTcv%2Fbtq0tXwhAXa%2FMalIJENxs3TAgGKjOlep2k%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;570&quot; height=&quot;372&quot; data-filename=&quot;스크린샷 2021-03-19 오후 1.06.30.png&quot; data-origin-width=&quot;586&quot; data-origin-height=&quot;372&quot;/&gt;&lt;/span&gt;&lt;/figure&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;span style=&quot;background-color: #f3c000;&quot;&gt;3. High-Level Kernel Initialization&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;호출된 main() 함수는 실제 커널의 초기화와 수행을 맡는 코드로, 주로 pintos 의 여러 모듈의 초기화 함수 호출로 이루어져 있으며, &lt;b&gt;&amp;lt;thread/init.c&amp;gt;&lt;/b&gt; 에 구현되어 있다. 모듈 호출 및 내용은 아래 순서별로 정리한다.&lt;/span&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;span style=&quot;color: #333333;&quot;&gt;bss_init() : 커널의 &quot;BSS&quot; 를 초기화한다.&lt;/span&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: #333333;&quot;&gt;BSS : bss(block started symbol) segment 를 뜻하고 이는 초기화 되지 않은 전역 변수와 정적 변수가 저장되는 공간이다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;보통 메모리는 5개의 세그먼트로 나누어 지는데, 가장 낮은 주소에서부터 &lt;b&gt;텍스트 세그먼트&lt;/b&gt;, &lt;b&gt;데이터 세그먼트&lt;/b&gt;, &lt;b&gt;bss 세그먼트&lt;/b&gt;, &lt;b&gt;힙 세그먼트&lt;/b&gt;, &lt;b&gt;스택 세그먼트&lt;/b&gt; 의 5종류이다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;텍스트 세그먼트는 코드(Code) 세그먼트라고도 하며 프로그램의 기계어 코드가 저장되어 읽기만 가능하고 쓰기는 불가능하다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;데이터 세그먼트는 초기화가 된 전역 변수와 정적 변수가 저장되는 공간이다. (정적 변수란 static 키워드를 통해 선언된 변수를 말함)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;힙 세그먼트 : 사용자가 직접 할당한 메모리가 저장되는 영역. malloc() 함수를 통해 할당 가능하다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;스택 세그먼트 : 지역 변수와 각종 정보들이 저장되는 영역으로 저장되는 방향은 힙 세그먼트와 반대로 주소의 높은 곳부터 낮은 곳으로 저장된다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;read_command_line() : kernel command line 을 읽어와서 arguments 로 나눈다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;parse_options() : command line 에서 options 을 읽어온다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;thread_init() : 쓰레드 시스템을 초기화한다. &lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;console_init() : 콘솔 초기화 -&amp;gt; 콘솔 초기화 이후에는 printf() 를 사용 가능하다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;커널의 메모리 시스템 초기화&lt;/span&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: #333333;&quot;&gt;palloc_init() : page allocator 설정&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;malloc_init() : 사용자 메모리 할당(malloc 함수) 이 가능하게 설정&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;paging_init() : loader.S 에서 구성했던 페이지 테이블을 다시 구성한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;tss_init() : tss(task state segment) 를 설정한다. 이는 커널이 task 를 관리할 때 필요한 정보가 들어있는 segment이다.&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;gdt_init() : gdt(global description table) 을 초기화한다. 마찬가지로 커널이 task 를 관리할 때 필요한 정보가 들어있다.&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;인터럽트 초기화&lt;/span&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: #333333;&quot;&gt;intr_init() : IDT(Interrupt Descriptor Table) 를 초기화한다. 이 table 은 인터럽트를 handling 하는 handler 함수들이&amp;nbsp; 연결되는 table이다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;timer_init(), kbd_init() : 타이머, 키보드 인터럽트 초기화&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;input_init() : input 모듈 초기화&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;exception_init(), syscall_init() : 예외처리 인터럽트, system call 인터럽트 초기화&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;thread_start() : 우선 가장 실행 우선순위가 낮은 idle 이라는 thread 를 생성하여 동작시키고 인터럽트를 활성화시킨다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;serial_init_queue() : serial 로부터 인터럽트를 받아 커널을 제어할 수 있도록 한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;timer_calibrate() : 정확한 시간 측정을 위해 timer 를 보정한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;ide_init() : IDE disk 를 초기화한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;filesys_init() : 파일시스템을 초기화한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&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;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;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;Pintos Documentation : &lt;a href=&quot;https://web.stanford.edu/class/cs140/projects/pintos/pintos.pdf&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;web.stanford.edu/class/cs140/projects/pintos/pintos.pdf&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Luavis'Dev Story - A20 gate : &lt;a href=&quot;https://b.luavis.kr/os/a20&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;b.luavis.kr/os/a20&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Science Direct - Page Directory Table : &lt;a href=&quot;https://www.sciencedirect.com/topics/computer-science/page-directory-table&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;www.sciencedirect.com/topics/computer-science/page-directory-table&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프로젝트/Pintos</category>
      <category>a20 gate</category>
      <category>LOADER</category>
      <category>Loading</category>
      <category>page table</category>
      <category>pintos</category>
      <category>Protected Mode</category>
      <category>realmode</category>
      <author>aa_rong_blog</author>
      <guid isPermaLink="true">https://poalim.tistory.com/23</guid>
      <comments>https://poalim.tistory.com/23#entry23comment</comments>
      <pubDate>Fri, 19 Mar 2021 13:48:41 +0900</pubDate>
    </item>
    <item>
      <title>[Pintos] 디버깅 도구 (Debugging Tools) - printf, assert, __attributes__, backtraces</title>
      <link>https://poalim.tistory.com/22</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;디버깅이란 프로그램에서 발생할 수 있는 버그(에러) 를 찾아서 없애는 과정을 말한다. 이 장에서는 Pintos 프로젝트를 진행하면서 활용할 수 있는 여러가지 디버깅 도구들에 대해 설명한다. 여기서 설명된 방법들은 Pintos 프로젝트 뿐 아니라 다른 프로젝트들에서도 널리 사용될 수 있는 디버깅 방법들이므로 익혀두도록 하자.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. printf()&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;printf() 를 c 언어의 가장 처음에 배우기 때문에 printf() 를 사용한 디버깅은 없어보인다고 생각하기 쉽다. 하지만 printf() 만큼 간단하고 유용하게 사용할 수 있는 Debugging Tools 도 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그램이 비정상적으로 작동할 때 printf() 를 코드의 중간중간 섞어 놓으면 문제가 발생한 코드의 범위를 특정할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ex) code A - printf(&quot;after A&quot;) - code B - printf(&quot;after B&quot;) - code C&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행결과 : after A (after B 는 출력되지 않음)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; code B 에 문제가 있음.&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;2. ASSERT&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;assert 함수는 표준 c 라이브러리 중 하나인 assert.h 에 포함되어 있는 매크로이다.&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;pintos 에서는 &lt;b&gt;&amp;lt;lib/debug.h&amp;gt;&lt;/b&gt; 에 간략하게 구현되어 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1616647037674&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* lib/debug.h */
#ifndef NDEBUG
#define ASSERT(CONDITION)                                       
        if (CONDITION) { } else {                               
                PANIC (&quot;assertion `%s' failed.&quot;, #CONDITION);   
        }
#define NOT_REACHED() PANIC (&quot;executed an unreachable statement&quot;);

#else
#define ASSERT(CONDITION) ((void) 0)
#define NOT_REACHED() for (;;)
#endif&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;b&gt;NDEBUG&lt;/b&gt; 라는 것은 디버그 모드가 아닌 경우(릴리즈 모드)를 말한다. 디버그 모드에서는 ASSERT 등의 디버깅 도구들이 작동하지만 릴리즈 모드에서는 디버깅 도구들은 무시하기 때문에 더 최적화된 실행파일이 만들어진다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;(&lt;b&gt;#ifndef&lt;/b&gt; 는 뒤 매크로가 정의되어 있지 않을 때 실행된다. &lt;b&gt;#ifdef&lt;/b&gt; 와 헷갈리지 말도록 하자.)&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;&lt;span&gt;&lt;b&gt;ASSERT&lt;/b&gt; 매크로는 정해진 조건에 맞지 않을 때 프로그램을 중단한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;위 코드에서 보면 조건(CONDITION) 이 참(true)이면 그냥 지나가지만, 거짓(false)라면 커널을 PANIC 에 빠뜨리고 Panic message(문제가 된 부분의 expression, file and line number, backtrace 등)을 출력한다. 즉, 프로그램이 올바로 작동하기 위해서는 assert 뒤의 조건이 항상 참(true)이 되도록 만들어야 한다.&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;printf() 와 마찬가지로 버그가 의심되는 곳에 여기저기 섞어두면 유용하다.&lt;/span&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;3. Function and Parameter Attributes&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pintos 의 &lt;b&gt;&amp;lt;lib/debug.h&amp;gt;&amp;nbsp;&lt;/b&gt;에는 함수 혹은 함수 파라미터에 사용할 수 있는 특별한 속성(attributes) 들이 정의되어 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1616650277603&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* lib/debug.h */
#define UNUSED __attribute__ ((unused))
#define NO_RETURN __attribute__ ((noreturn))
#define NO_INLINE __attribute__ ((noinline))
#define PRINTF_FORMAT(FMT, FIRST) __attribute__ ((format (printf, FMT, FIRST)))&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 __attribute__ 라는 표현이 생소할 수도 있다. __attribute__ 표현은 gcc 에서 사용할 수 있는 확장 기능정도로 볼 수 있다. 이는 함수, 구조체, 변수 등의 컴파일 시에 특정 속성을 적용시키는 역할을 한다. &quot;__attribute__ ((속성인자))&quot; 의 형태로 사용하면 이 표현이 붙은 함수, 구조체 혹은 변수에 지정한 속성을 부여할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, __attribute__ ((unused)) 는 특정 변수에 unused 라는 속성을 부여하는데 사용될 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1616651270214&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* www.keil.com/support/man/docs/armcc/armcc_chr1359124982981.htm */
void Variable_Attributes_unused_0() {
	static int aStatic =0; 
    int aUnused __attribute__((unused)); 
    int bUnused; 
    aStatic++; 
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 실행시키면 원래라면 사용하지 않은 aUnused, bUnused 라는 변수에 대해 컴파일러가 경고를 하지만, unused 속성을 부여받은 aUnused 변수에 대하여는 경고를 하지 않는다.&lt;/p&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;&amp;lt;lib/debug.h&amp;gt;&lt;/b&gt; 를 해석해보자.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;UNUSED&lt;/b&gt; : 함수의 parameter 에 붙어서 해당 parameter 가 사용되지 않을 수 있음을 명시한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;NO_RETURN&lt;/b&gt; : 함수 prototype 에 붙어서 해당 함수가 return 되지 않음을 명시한다. 이는 단순히 return 값이 없다는 것 이상으로 이 함수를 call 한 caller 에게로 control 이 돌아가지 않음을 의미한다. 따라서 NO_RETURN 속성을 가지는 함수 이후의 코드는 컴파일러에 의해 도달할 수 없는 코드로 최적화되어 제거된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1616666714970&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;__attribute__ ((noreturn)) void noReturnf(){ ... }

int main(){
    print(&quot;a&quot;);
    noReturnf();
    print(&quot;b&quot;);		// unreachable
    print(&quot;c&quot;);		// unreachable
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;NO_INLINE&lt;/b&gt; : 함수 prototype 에 붙어서 해당 함수가 inline 처리되지 않음을 명시한다. inline 으로 정의된 함수는 컴파일 할 때 함수가 사용되는 모든 곳에 함수의 코드를 복사하여 넣어준다. 즉, 프로그램이 실행되면서 만나는 function call 에서 함수를 호출하지 않고 함수의 코드를 그대로 실행한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;730&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dsyJ9z/btq09jy1j2R/Uze0nvnoKCEeAjfBf4FBLK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dsyJ9z/btq09jy1j2R/Uze0nvnoKCEeAjfBf4FBLK/img.png&quot; data-alt=&quot;이미지출처 : C언어 코딩 도장 (일반 함수와 인라인 함수의 차이)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dsyJ9z/btq09jy1j2R/Uze0nvnoKCEeAjfBf4FBLK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdsyJ9z%2Fbtq09jy1j2R%2FUze0nvnoKCEeAjfBf4FBLK%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;553&quot; height=&quot;730&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;730&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이미지출처 : C언어 코딩 도장 (일반 함수와 인라인 함수의 차이)&lt;/figcaption&gt;
&lt;/figure&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;PRINT_FORMAT (format, first)&amp;nbsp;&lt;/b&gt;: 함수 prototype 에 붙어서 해당 함수가 printf 함수의 형식으로 사용됨을 명시한다. format 은 format string 의 위치이고, first 는 value parameter 의 시작 위치를 나타낸다. 이렇게 말하면 이해가 잘 되지 않으니 예시를 들어보겠다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1616806453633&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* lib/debug.h */
void debug_panic (const char *file, int line, const char *function,
                  const char *message, ...) PRINTF_FORMAT (4, 5) NO_RETURN;&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;위 함수는 pintos 의 panic message 를 출력해주는 디버깅함수이다. 이 함수에 PRINT_FORMAT (4, 5) 가 붙었으므로 이 함수의 parameter 는 printf 함수의 형식을 따라야 하고, 그렇지 않을 시 오류를 출력한다. 즉 format string 의 위치로 지정된 4번째 parameter 인 const char *message 에는 format string (ex. &quot;%d + %d = %d&quot; 등) 의 형식 문자열이 argument 로 들어와야 하고, value parameter 의 시작 위치로 지정된 5번째 parameter 부터는 format string 의 %d, %s, %c 등에 들어갈 값들이 알맞은 type 으로 입력되어야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1616807654860&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;debug_panic ([blah], [blah], [blah], &quot;error message here, line %d&quot;, 4)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시처럼 앞에 3개의 parameter 를 생략하고 보면 printf 의 형식과 같다. 만약 5번째 argument 로 4 대신 &quot;c&quot; 등의 다른 type 의 argument 가 들어오면 컴파일러가 경고 문구로 알려주게 된다.&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;4. Backtraces&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kernel panic 이 발생했을 때 프로그램이 오류가 발생한 지점의 address 를 역추적하여 알려준다. &lt;b&gt;&amp;lt;lib/debug.h&amp;gt;&lt;/b&gt; 에 debug_backtrace(), debug_backtrace_all() 로 구현되어 있으며 각각 현재 지점의 backtrace, 모든 threads 의 backtrace 를 출력한다. 이 함수는 역추적한 address 들을&amp;nbsp; hexadecimal 값으로 출력하여 주기 때문에 해석에 곤란함을 겪을 수 있다. 이를 위해, pintos 는 터미널에서 backtrace 라는 명령어를 제공하여 이 hexadecimal 값들을 function name, source file line 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;example) debug_backtrace() 의 결과가 아래와 같이 출력되었다고 하자.&lt;/p&gt;
&lt;pre id=&quot;code_1616809087210&quot; class=&quot;r&quot; data-ke-language=&quot;r&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Call stack : 0xc0106eff 0xc01102fb 0xc010dc22 0xc010cf67 0xc0102319 0xc010325a 0xc804812c 0x8048196 0x8048ac8&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;결과의 해석은 backtrace [kernel.o(backtrace 를 제공한 kernel)] [hexadecimal numbers] 형식으로 사용한다.&lt;/p&gt;
&lt;pre id=&quot;code_1616809303318&quot; class=&quot;r&quot; data-ke-language=&quot;r&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ backtrace kernel.o 0xc0106eff 0xc01102fb 0xc010dc22 0xc010cf67 0xc0102319 
                     0xc010325a 0xc804812c 0x08048196 0x08048ac8&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1616809457545&quot; class=&quot;r&quot; data-ke-language=&quot;r&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# result
0xc0106eff: debug_panic (lib/debug.c:86)
0xc01102fb: file_seek (filesys/file.c:405)
0xc010dc22: seek (userprog/syscall.c:744)
0xc010cf67: syscall_handler (userprog/syscall.c:444)
0xc0102319: intr_handler (threads/interrupt.c:334)
0xc010325a: intr_entry (threads/intr-stubs.S:38)
0xc804812c: (unknown)
0x08048196: (unknown)
0x08048ac8: (unknown)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 3줄이 (unknown) 으로 표시된 것은 이들이 kernel 함수가 아니라 user program 의 함수이기 때문이다. 만약 어떤 user program 에서 kernel panic 이 발생했는지 알고있다면 해당 user program 에서 다시 backtrace 를 실행하면 (unknown) 의 정체를 알 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1616809919297&quot; class=&quot;r&quot; data-ke-language=&quot;r&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ backtrace tests/filesys/extended/grow-too-big 0xc0106eff 0xc01102fb 
                                                0xc010dc22 0xc010cf67
                                                0xc0102319 0xc010325a
                                                0xc804812c 0x08048196 
                                                0x08048ac8&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1616810070349&quot; class=&quot;r&quot; data-ke-language=&quot;r&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# result
0xc0106eff: (unknown)
0xc01102fb: (unknown)
0xc010dc22: (unknown)
0xc010cf67: (unknown)
0xc0102319: (unknown)
0xc010325a: (unknown)
0xc804812c: test_main (...xtended/grow-too-big.c:20)
0x08048196: main (tests/main.c:10)
0x08048ac8: _start (lib/user/entry.c:9)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번엔 kernel 함수들이 (unknown) 으로 표시되고 user program 함수들만 제대로 나온 것을 볼 수 있다. 당연한 얘기지만, 이 출력값은 Call stack 의 출력이므로 아래서부터 위로 올라가며 함수를 호출한 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1616810237637&quot; class=&quot;r&quot; data-ke-language=&quot;r&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ backtrace kernel.o tests/filesys/extended/grow-too-big ...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 식으로 kernel 과 user program 을 동시에 넣으면 (unknown) 없이 한 번에 모든 값이 출력된다.&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;5. GDB&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GDB 의 내용은 워낙 방대한 관계로 따로 포스팅하도록 한다.&lt;/p&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;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;Reference)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://web.stanford.edu/class/cs140/projects/pintos/pintos.pdf&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;web.stanford.edu/class/cs140/projects/pintos/pintos.pdf&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.keil.com/support/man/docs/armcc/armcc_chr1359124982981.htm&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;www.keil.com/support/man/docs/armcc/armcc_chr1359124982981.htm&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://dojang.io/mod/page/view.php?id=748&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;dojang.io/mod/page/view.php?id=748&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;</description>
      <category>프로젝트/Pintos</category>
      <category>ASSERT</category>
      <category>backtrace</category>
      <category>os</category>
      <category>pintos</category>
      <category>printf</category>
      <category>__attributes__</category>
      <author>aa_rong_blog</author>
      <guid isPermaLink="true">https://poalim.tistory.com/22</guid>
      <comments>https://poalim.tistory.com/22#entry22comment</comments>
      <pubDate>Tue, 16 Mar 2021 10:56:10 +0900</pubDate>
    </item>
    <item>
      <title>MKS 하프마라톤(21.1 km) - 2021.03.02</title>
      <link>https://poalim.tistory.com/20</link>
      <description>
&lt;p data-ke-size=&quot;size16&quot;&gt;어느덧 매일 아침 5km 를 뛰면서 시작하는 하루가 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;매일 뛰는 것도 좋지만 뭐니뭐니 해도 러닝의 꽃은 마라톤이겠죠. 이제 어엿한 한 사람의 러닝인으로써 2021년 저의 가장 큰 목표 중 하나는 '하프마라톤 완주' 였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;러닝을 시작한 지 오래 되지도 않았고 21.1km 라는 어마어마한 거리에 겁을 먹었기에 1년간 꾸준히 단련해서 연말쯤 도전을 해 볼 생각이었습니다. 그러다 어느 날 MKS 마라톤이 언택트로 진행된다는 소식에 10km 부문을 신청하려고 들어가서 저도 모르게 하프마라톤을 신청해버렸습니다 .. ㅎㅎ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;과연 결과는?&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;731&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGFxQT/btqZoydjN6d/tjVk9oQeCXRbGmN5Is37bk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGFxQT/btqZoydjN6d/tjVk9oQeCXRbGmN5Is37bk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGFxQT/btqZoydjN6d/tjVk9oQeCXRbGmN5Is37bk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGFxQT%2FbtqZoydjN6d%2FtjVk9oQeCXRbGmN5Is37bk%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;731&quot; height=&quot;1080&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;731&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&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;2시간 48분 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;전체 58명 중 55위... 이지만 완주했다는 사실이 중요한 것 아니겠어요?? 꾸준히 연습해서 연말에는 더 좋은 기록도 욕심을 내보려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;10km 마라톤 완주 경험이 있어서 10km 까지는 생각보다 쉽게 통과했는데 문제는 15km 부근이었습니다. 숨이 차는 거는 둘째치고 정말로 다리가 내 맘대로 움직이지가 않더라구요. 종아리부터 허벅지까지 모든 근육이 더이상 움직이지 않아서 길바닥에 주저 앉아서 다리 마사지를 한참을 해주고 나서야 다시 뛰어 나갈 수 있었습니다.&lt;/p&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년간 다니고 졸업과 취업을 앞두고 있는 상황에서 인생에 대한 생각이 많아지던 저는 이 문구를 보고 인생은 잘 모르겠으니 마라톤을 뛰어보자 라는 생각을 했었던 것 같아요.&lt;/p&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;하지만 직접 하프 마라톤을 뛰어보면서 느낀 것은 전혀 달랐습니다. 10km, 15km, 20km 등 어느정도 스스로 생각하는 기준점이 다가올 때마다 '이정도면 되지 않았나?' 하는 생각이 끊임없이 저를 멈추고 싶게 만들었습니다. 이 정도면 이미 어느 정도 해냈다는 마음과 10km, 15km 도 뛰지 못하는 또래들이 대부분일 것이라 등의 생각이 끊임없이 스스로를 합리화시키려고 하는 것을 느꼈습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 유혹을 모두 이겨내고 완주하고 나니 멈추지 않고 꾸준히 달리는 것도 중요하지만 이것보다 가장 중요한 것은 &lt;b&gt;'끝까지 가겠다는 의지'&lt;/b&gt; 라는 것을 느꼈습니다. 아무리 중간까지 빠르게 달렸다고 해도 거기서 만족하고 그만 뛰면 의미가 없겠죠. 시간이 많이 걸리고 여러가지 핑계들이 스스로를 합리화하려고 해도 처음에 생각했던 목표를 변함없이 가지고 끝까지 가려는 확고한 마음을 가진 사람만이 완주를 해낼 수 있다는 생각을 하였습니다.&lt;/p&gt;</description>
      <category>일상/러닝(마라톤 기록)</category>
      <author>aa_rong_blog</author>
      <guid isPermaLink="true">https://poalim.tistory.com/20</guid>
      <comments>https://poalim.tistory.com/20#entry20comment</comments>
      <pubDate>Sat, 6 Mar 2021 16:58:19 +0900</pubDate>
    </item>
    <item>
      <title>[sftp] vscode 에서 서버 연결하기</title>
      <link>https://poalim.tistory.com/19</link>
      <description>
&lt;p data-ke-size=&quot;size16&quot;&gt;vscode 의 &quot;sftp&quot; 확장 프로그램을 쓰면 서버의 소스를 로컬에서 vscode로 편집할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 가상머신의 리눅스 환경에서 프로젝트를 진행하다가 vim 보다는 아무래도 vscode 가 익숙하여 로컬 vscode 에서 서버를 연결하여 사용하였고 그 방법을 공유하려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;*서버에 ssh 가 설치되어 있지 않아서 sftp 이용이 불가할 수 있으니 sudo dpkg -l | grep ssh 로 확인해보시고 없다면 sudo apt-get install ssh 로 ssh 를 먼저 설치해주셔야 합니다.*&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. SFTP 확장 프로그램 설치&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vscode 에서 sftp 를 사용하기 위해 확장 프로그램을 설치하여야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- vscode 에서 왼쪽 메뉴의 저 테트리스 같은 모양을 누르면 확장 프로그램들을 설치할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- sftp 를 섬색 후 install 버튼을 눌러 설치해줍니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/I0AMX/btqYKaEhOO4/2TY7pVTLFum4Vu2UST8bbk/img.png&quot; data-image-src=&quot;https://blog.kakaocdn.net/dn/I0AMX/btqYKaEhOO4/2TY7pVTLFum4Vu2UST8bbk/img.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;서버에 연결될 로컬 공간을 만들어줘야 합니다. 저는 헷갈리지 않게 폴더명을 서버 주소로 하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. 환경설정&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;환경설정을 하기 전에 먼저 vscode 의 File 메뉴에서 open folder 를 눌러서 방금 만든 폴더를 열어줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-filename=&quot;화면 캡처 2021-03-01 114940.jpg&quot; data-origin-width=&quot;430&quot; data-origin-height=&quot;380&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NiMxG/btqYTOGVnGS/1niPFzRkkTLgh7Ij6O2SG1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NiMxG/btqYTOGVnGS/1niPFzRkkTLgh7Ij6O2SG1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NiMxG/btqYTOGVnGS/1niPFzRkkTLgh7Ij6O2SG1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNiMxG%2FbtqYTOGVnGS%2F1niPFzRkkTLgh7Ij6O2SG1%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;430&quot; height=&quot;380&quot; data-filename=&quot;화면 캡처 2021-03-01 114940.jpg&quot; data-origin-width=&quot;430&quot; data-origin-height=&quot;380&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 후 f1 키를 눌르고 SFTP: Config 입력 후 엔터를 누르시면 .vscode 라는 폴더와 그 아래 sftp.json 파일이 생깁니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;649&quot; data-origin-height=&quot;98&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PYF9S/btqYZ5H8bFF/ZD3ZImtvHO1kuw0K0gg571/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PYF9S/btqYZ5H8bFF/ZD3ZImtvHO1kuw0K0gg571/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PYF9S/btqYZ5H8bFF/ZD3ZImtvHO1kuw0K0gg571/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPYF9S%2FbtqYZ5H8bFF%2FZD3ZImtvHO1kuw0K0gg571%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;649&quot; height=&quot;98&quot; data-origin-width=&quot;649&quot; data-origin-height=&quot;98&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 sftp.json 파일을 눌러서 수정하여 환경설정을 할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-filename=&quot;화면 캡처 2021-03-01 115617.jpg&quot; data-origin-width=&quot;715&quot; data-origin-height=&quot;498&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/y8dJu/btqYW2LGabK/QdEkGcCVUzZHlr5QFIi2fK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/y8dJu/btqYW2LGabK/QdEkGcCVUzZHlr5QFIi2fK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/y8dJu/btqYW2LGabK/QdEkGcCVUzZHlr5QFIi2fK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fy8dJu%2FbtqYW2LGabK%2FQdEkGcCVUzZHlr5QFIi2fK%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;715&quot; height=&quot;498&quot; data-filename=&quot;화면 캡처 2021-03-01 115617.jpg&quot; data-origin-width=&quot;715&quot; data-origin-height=&quot;498&quot;/&gt;&lt;/span&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;name : 이 연결을 설명할 수 있는 이름을 정해서 적으시면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;host : 연결할 서버 주소 (ex. 192.168.0.1)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;username : 연결할 서버의 username&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;password : 비밀번호 (보안 상의 문제가 걱정된다면 rsa 키를 이용한 로그인 사용 -&amp;gt; &quot;privateKeyPath&quot; : &quot;rsa키 경로&quot;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;remotePath : 로컬과 연결할 서버의 Path. 처음 입력되어 있는 &quot;/&quot; 를 그대로 두면 root 디렉토리로 연결됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;uploadOnSave : 이 옵션을 true 로 해두면 로컬에서 작업하고 저장할때마다 서버로 변경사항이 동기화됩니다.&lt;/p&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;h4 data-ke-size=&quot;size20&quot;&gt;4. 연결&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 후 f1 키를 누른 후 SFTP : List All 을 입력하여 누른 후 연결할 서버 주소를 클릭하면 연결이 됩니다.&lt;/p&gt;</description>
      <category>Programming/기타</category>
      <category>SFTP</category>
      <category>vscode</category>
      <author>aa_rong_blog</author>
      <guid isPermaLink="true">https://poalim.tistory.com/19</guid>
      <comments>https://poalim.tistory.com/19#entry19comment</comments>
      <pubDate>Mon, 1 Mar 2021 12:05:43 +0900</pubDate>
    </item>
    <item>
      <title>VIRTUAL 춘천마라톤 (10 km) - 2020.11.26</title>
      <link>https://poalim.tistory.com/18</link>
      <description>
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;지난 10월 24일 코로나19와 학교 생활로 운동량은 줄어들고 식습관은 배달음식들로 채워지고 불규칙한 생활패턴이 너무도 자연스러워진 내 모습을 보며 운동을 시작해야겠다고 마음을 먹었다.&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;&lt;span&gt;그렇게 시작하게 된 러닝.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;처음엔 가볍게 하루 5km 씩 매일 꾸준히 뛰어보자! 라는 마음가짐으로 시작했는데 하루하루 실천해 나가다 보니까 매일 5km 씩이나 뛴다는게 생각보다 엄청난 부지런함과 의지를 필요로 한다는 것을 알게되었다.&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;&lt;span&gt;일단 하기로 마음먹었기에 한 달을 꾸준히 매일 5km 로 채우고 드디어 첫 번째 결실을 맺기 위해서 10km 마라톤에 도전했다. &lt;/span&gt;&lt;span&gt;매일 5km 씩 뛰는게 익숙해져서 10km 도 할만하지 않을까 라는 가벼운 마음으로 나갔는데 8km쯤부터 차원이 다르게 힘들어서 여기서 멈출까 백만번 생각했지만 아침 해가 뜨는거를 보면서 버텼다&lt;/span&gt;&lt;span&gt; &lt;/span&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;widthContent&quot; data-filename=&quot;KakaoTalk_20210225_073752483.jpg&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;1440&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dgGful/btqYvd1z5VH/FaxOe7HwE6lQVnt3XdlqBK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dgGful/btqYvd1z5VH/FaxOe7HwE6lQVnt3XdlqBK/img.jpg&quot; data-alt=&quot;10km 완주 기록 및 배번호표&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dgGful/btqYvd1z5VH/FaxOe7HwE6lQVnt3XdlqBK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdgGful%2FbtqYvd1z5VH%2FFaxOe7HwE6lQVnt3XdlqBK%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;598&quot; height=&quot;1440&quot; data-filename=&quot;KakaoTalk_20210225_073752483.jpg&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;1440&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;10km 완주 기록 및 배번호표&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;결과는 총 거리 10.01km, 시간 1:01:20, 페이스 6'&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;08''(1km 당 평균 6분 08초)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;조금만 더 빨리 뛰었으면 1시간 안에 들어올 수 있었는데 라는 생각에 아쉽기도 하지만 그래도 10km 채우고 나니 해냈다는 생각에 너무 뿌듯했다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span&gt;&lt;/span&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. 아침에 일어나자마자 빵 하나와 바나나 한 개만 먹고 뛰러 나갔는데, 마라톤 전에 든든한 음식 섭취는 필수인 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 부드러운 재질의 의류를 착용하자. 겨드랑이, 사타구니 등이 쓸려서 한동안 고생할 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 바람이 심한 날에는 고글을 착용하자. 찬바람에 눈물이 말라서 눈을 뜨기가 힘들었다.&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;widthContent&quot; data-filename=&quot;KakaoTalk_20210225_114518198.jpg&quot; data-origin-width=&quot;715&quot; data-origin-height=&quot;715&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9VDSp/btqYxp1EYWD/TtUZAtVPR4ICPlXkBhUqA1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9VDSp/btqYxp1EYWD/TtUZAtVPR4ICPlXkBhUqA1/img.jpg&quot; data-alt=&quot;완주 기념 메달&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9VDSp/btqYxp1EYWD/TtUZAtVPR4ICPlXkBhUqA1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9VDSp%2FbtqYxp1EYWD%2FTtUZAtVPR4ICPlXkBhUqA1%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;562&quot; height=&quot;715&quot; data-filename=&quot;KakaoTalk_20210225_114518198.jpg&quot; data-origin-width=&quot;715&quot; data-origin-height=&quot;715&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;br /&gt;&lt;b&gt;&lt;span&gt;꾸준히 뛰어서 내년엔 하프 마라톤 도전 &lt;/span&gt;&lt;/b&gt;&lt;/p&gt;</description>
      <category>일상/러닝(마라톤 기록)</category>
      <category>건강관리</category>
      <category>러닝</category>
      <category>마라톤</category>
      <category>운동</category>
      <category>춘천마라톤</category>
      <category>취미생활</category>
      <author>aa_rong_blog</author>
      <guid isPermaLink="true">https://poalim.tistory.com/18</guid>
      <comments>https://poalim.tistory.com/18#entry18comment</comments>
      <pubDate>Thu, 25 Feb 2021 08:39:50 +0900</pubDate>
    </item>
    <item>
      <title>[Vim]vim 기본설정 및 팁</title>
      <link>https://poalim.tistory.com/15</link>
      <description>
&lt;p data-ke-size=&quot;size16&quot;&gt;VIM(VI iMproved) : 리눅스 터미널 환경에서 사용가능한 대표적인 텍스트 편집기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vim 의 설정값은 .vimrc 파일을 수정함으로 변경할 수 있고, .vimrc 파일은 언제나 홈 디렉토리에 존재해야 한다.&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;1. .vimrc 파일 생성하기&lt;/h3&gt;
&lt;pre id=&quot;code_1607598314834&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cd ~
vim .vimrc&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. .vimrc 파일 설정하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자가 사용하는 개인 설정값을 아래 첨부합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-filename=&quot;스크린샷 2020-12-10 오후 8.15.46.png&quot; data-origin-width=&quot;453&quot; data-origin-height=&quot;506&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xXN15/btqPQsssY0q/zM6aHhDpGNx1AzqxXvl4fK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xXN15/btqPQsssY0q/zM6aHhDpGNx1AzqxXvl4fK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xXN15/btqPQsssY0q/zM6aHhDpGNx1AzqxXvl4fK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxXN15%2FbtqPQsssY0q%2FzM6aHhDpGNx1AzqxXvl4fK%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;453&quot; height=&quot;506&quot; data-filename=&quot;스크린샷 2020-12-10 오후 8.15.46.png&quot; data-origin-width=&quot;453&quot; data-origin-height=&quot;506&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복사해가실 분은 아래 코드블럭을 사용해주시면 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1607599043664&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if has(&quot;syntax&quot;)
    syntax on
endif

set autoindent
set cindent

set ts=4
set sts=4
set shiftwidth=4

set laststatus=2
set hlsearch
set scrolloff=2
set showmatch

set nocompatible
set mouse=a

set smartcase
set smarttab
set smartindent

set ruler

set fileencodings=utf8,euc-kr&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 기타 팁&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vim 에서 마우스를 이용하여 드래그 한 뒤 복사를 하면 제대로 복사가 되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;'option + 드래그' 를 사용하면 진하게 드래그가 되며, 복사가 제대로 된다.&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;vim 단축키 정리 사이트 :&amp;nbsp;&lt;a href=&quot;https://gmlwjd9405.github.io/2019/05/14/vim-shortkey.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;gmlwjd9405.github.io/2019/05/14/vim-shortkey.html&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;- 3번 항목은 탭은 새로운 팁을 발견할 때마다 업데이트 할 예정&lt;/p&gt;</description>
      <category>Programming/기타</category>
      <category>vim</category>
      <author>aa_rong_blog</author>
      <guid isPermaLink="true">https://poalim.tistory.com/15</guid>
      <comments>https://poalim.tistory.com/15#entry15comment</comments>
      <pubDate>Thu, 10 Dec 2020 20:58:36 +0900</pubDate>
    </item>
    <item>
      <title>[알고리즘] 동적 계획법(Dynamic Programming) - 기본</title>
      <link>https://poalim.tistory.com/14</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;동적 계획법(Dynamic Programming)&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;짧게 줄여서 DP 라고 부르는 이 알고리즘은 분할정복(Divide and Conquer) 알고리즘과 마찬가지로 복잡한 문제를 여러 개의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;부분 문제들(Sub-problem) 으로 나누어&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;푼 후, 그 결과를 이용하여 복잡한 문제를 푸는 알고리즘이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 분할정복 알고리즘과의 차이는 DP 에서는 각 부분 문제들간에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;겹치는 부분&lt;/b&gt;이 존재한다는 것인데, DP 는 이러한 겹치는 부분들을&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;메모이제이션(Memoization)&lt;/b&gt;&lt;span&gt;&amp;nbsp;&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;&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&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;318&quot; data-origin-height=&quot;130&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLhDrS/btqPKtE0C6s/z2cdU6pUWvZ4Ye9Akezfx1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLhDrS/btqPKtE0C6s/z2cdU6pUWvZ4Ye9Akezfx1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLhDrS/btqPKtE0C6s/z2cdU6pUWvZ4Ye9Akezfx1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLhDrS%2FbtqPKtE0C6s%2Fz2cdU6pUWvZ4Ye9Akezfx1%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;318&quot; height=&quot;130&quot; data-origin-width=&quot;318&quot; data-origin-height=&quot;130&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같이 Fib(0) ~ Fib(3) 은 동일한 값을 구하는 연산이 중복되어 나타나 시간의 낭비가 생기는 것을 알 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;749&quot; data-origin-height=&quot;324&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dacell/btqPLP13uQ5/tRG4rrJJ0k1sOtQwimWD01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dacell/btqPLP13uQ5/tRG4rrJJ0k1sOtQwimWD01/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dacell/btqPLP13uQ5/tRG4rrJJ0k1sOtQwimWD01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdacell%2FbtqPLP13uQ5%2FtRG4rrJJ0k1sOtQwimWD01%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;749&quot; height=&quot;324&quot; data-origin-width=&quot;749&quot; data-origin-height=&quot;324&quot;/&gt;&lt;/span&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;pre id=&quot;code_1607516416492&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int Fibonacci(int n){
    if (n == 0) return 0;
    if (n == 1) return 1;
    
    // 이미 계산한 값을 가지고 있으면 더이상 진행하지 않고 해당 값을 리턴
    if (seq[n] != -1) return seq[n];
    
    seq[n] = Fibonacci(n-1) + Fibonacci(n-2);
    return seq[n];
}&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;벡터의 초기화는 피보나치 수열에서 계산의 결과값으로 나올 수 없는 값인 -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;이처럼 메모이제이션을 사용하여 피보나치 수열을 구하는 알고리즘은 각 n 마다 하나의 값들을 구하면 되므로 O(n) 으로 일반적인 재귀형 알고리즘으로 피보나치 수열을 구현하였을 때 지수형 시간복잡도가 나오는 것과 비교하여 보면 매우 효과적이라고 볼 수 있습니다.&lt;/p&gt;</description>
      <category>Programming/Algorithms</category>
      <category>CS</category>
      <category>동적계획법</category>
      <category>알고리즘</category>
      <author>aa_rong_blog</author>
      <guid isPermaLink="true">https://poalim.tistory.com/14</guid>
      <comments>https://poalim.tistory.com/14#entry14comment</comments>
      <pubDate>Wed, 9 Dec 2020 21:24:42 +0900</pubDate>
    </item>
    <item>
      <title>[웹서버] 라즈베리파이로 홈 서버 만들기</title>
      <link>https://poalim.tistory.com/13</link>
      <description>&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;마침 요즘 사이드 프로젝트로 책을 읽고 독후감을 쓸 수 있는 웹 사이트를 하나 만들고 있었는데 이 웹을 aws 같은 호스팅 서비스를 이용하지 않고 라즈베리파이를 이용해서 집에 직접 서버를 운영하기로 했습니다.&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;1. 웹 서버 설치하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라즈베리 파이를 서버로 이용하기 위하여는 라즈베리 파이가 서버로서 동작할 수 있도록 웹 서버 소프트웨어를 설치해주어야 합니다. 일반적으로 많이 쓰이는 웹 서버 소프트웨어는 아파치(Apache), 엔진엑스(Nginx) 등이 있는데 라즈베리 파이라는 제한된 환경을 고려하여 아파치HTTP서버보다는 많이 가벼운 특징을 가지는 엔진엑스(Nginx) 를 사용하도록 했습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;설치하고 테스트하기&lt;/h4&gt;
&lt;pre id=&quot;code_1603954692005&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo apt-get install nginx&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;/p&gt;
&lt;pre id=&quot;code_1603954923685&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo /etc/init.d/nginx start&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 입력하여 nginx 서버를 실행시킨 후에, 라즈베리 파이와 같은 네트워크에 접속한 아무 기기로 라즈베리 파이의 IP 를 주소창에 입력하면 아래와 같은 기본 페이지가 나올 것입니다. 라즈베리 파이에서 접속할 땐 192.0.0.1 로 접속하면 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;514&quot; data-origin-height=&quot;266&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/U7XZM/btqL7PxS8Nt/vbwndu1tXkEryw3yECKKgk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/U7XZM/btqL7PxS8Nt/vbwndu1tXkEryw3yECKKgk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/U7XZM/btqL7PxS8Nt/vbwndu1tXkEryw3yECKKgk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FU7XZM%2FbtqL7PxS8Nt%2Fvbwndu1tXkEryw3yECKKgk%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;514&quot; height=&quot;266&quot; data-origin-width=&quot;514&quot; data-origin-height=&quot;266&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Nginx 웹 서버는 기본적으로 라즈베리 파이의 /var/www/html 이라는 폴더에 들어있고, 이 안에 index.html 이라는 페이지가 위 사진처럼 나오는 기본 페이지이고 이를 수정하여 나만의 페이지가 나오도록 할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. npm 설치&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라즈베리 파이에 주로 사용되는 운영체제는 라즈비안OS로 리눅스 기반의 운영체제 입니다. NodeJS 기반 프로젝트를 관리하기 위하여 npm 을 설치해주어야 하는데, 윈도우에서 설치할 때는 node.js 홈페이지에 들어가 LTS버전을 설치하면 되지만, 리눅스에서 패키지 관리자로 npm 을 설치하게 되면 상당히 지난 버전의 npm 이 설치됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1604008805227&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo apt-get install npm&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 apt-get 으로 npm 을 설치하면 지난 버전의 npm 이 설치되어 새로운 기능들이 실행되지 않는 경우가 많습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 원하는 버전의 npm 을 설치하려면&lt;/p&gt;
&lt;pre id=&quot;code_1604008885551&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash - 
sudo apt-get install -y nodejs&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;첫째 줄에 setup_ 다음에 나오는 12는 12 버전을 설치하겠다는 뜻이고 최신 버전을 확인하고 이 숫자를 바꾸어주면 해당하는 버전을 설치할 수 있습니다.&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;3. 프로젝트 실행하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 지금까지 로컬에서 작업하던 프로젝트를 라즈베리 파이로 옮겨와야 합니다. git 을 사용하여도 되고 ssh 를 사용한다면 scp 로 옮길 수도 있겠네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 git 을 사용하여 프로젝트 폴더를 옮겨왔습니다. 이제 옮겨온 프로젝트를 실행하면 되는데, 한 가지 문제가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제 웹 서버는 nodeJS 로 구현하였고, npm 으로 구동시키는데 일정 시간이 지나거나 여러 이벤트로 앱이 중단될 가능성이 있습니다. 이를 막고 node 서버가 중단되지 않고 영구적으로 구동되도록 하기 위해 NodeJS의 무중단 관리도구인 forever 를 사용했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;forever 는 node 앱을 실행시키고, 앱이 비정상종료되었을 때 자동으로 재실행을 시켜주는 관리도구입니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;설치 (forever)&lt;/h4&gt;
&lt;pre id=&quot;code_1603955751172&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm install forever -g&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;npm 을 이용하여 간단하게 패키지를 설치할 수 있으며 -g 로 글로벌로 적용되도록 해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설치가 완료되면 forever 명령어 몇 가지를 이용하여 앱을 실행시킬 수 있습니다!&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;forever 시작하기&lt;/h4&gt;
&lt;pre id=&quot;code_1603955876202&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;forever start app.js
forever start -c &quot;npm run start&quot; ./&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱을 실행시키는 명령어는 start 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 줄과 같이 app.js 파일을 바로 실행시킬 수도 있고, 두 번째 줄과 같이 npm run 명령어를 사용하여 구동시킬 수도 있습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;forever 목록&lt;/h4&gt;
&lt;pre id=&quot;code_1603956081139&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;forever list&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 명령어는 현재 forever 로 실행시킨 프로세스들의 리스트와 정보를 보여줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-filename=&quot;스크린샷 2020-10-29 오후 4.22.11.png&quot; data-origin-width=&quot;1682&quot; data-origin-height=&quot;106&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cJeDt4/btqL7PY0Wcl/SKjLDzeMa5uERbGfSsmPfK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cJeDt4/btqL7PY0Wcl/SKjLDzeMa5uERbGfSsmPfK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cJeDt4/btqL7PY0Wcl/SKjLDzeMa5uERbGfSsmPfK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcJeDt4%2FbtqL7PY0Wcl%2FSKjLDzeMa5uERbGfSsmPfK%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;1682&quot; height=&quot;106&quot; data-filename=&quot;스크린샷 2020-10-29 오후 4.22.11.png&quot; data-origin-width=&quot;1682&quot; data-origin-height=&quot;106&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 목록에서 uid 앞에 [0] 으로 각각의 프로세스 번호가 붙는데 이 번호를 이용해 프로세스를 종료하거나 재시작 할 수 있습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;forever 종료&lt;/h4&gt;
&lt;pre id=&quot;code_1603956270543&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;forever stop [번호]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;stop 명령어는 해당 번호에 해당하는 forever 프로세스를 정지시킵니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;forever 재시작&lt;/h4&gt;
&lt;pre id=&quot;code_1603956340471&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;forever restart [번호]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;restart 명령어로 프로세스를 재시작 할 수 있습니다. 저는 이거를 은근 편리하게 잘 사용했습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;stop, kill, start 이 세 명령어로 처리되는 과정을 하나의 명령어로 퉁치는게 되니 생각 이상으로 편리하더라구요.&lt;/p&gt;

&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 포트포워딩&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버를 실행시켰으면 이제 포트포워딩을 통해 이 서버에 외부에서 접속할 수 있도록 해야합니다. 이건 따로 포스트를 하였으니 링크로 들어가서 확인해주세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://poalim.tistory.com/12&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;poalim.tistory.com/12&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1603956744626&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[포트포워딩] 라즈베리파이 외부접속하기&quot; data-og-description=&quot;안녕하세요, 오늘 다를 주제는 라즈베리 파이에 관한 주제입니다. 요즘 사이드프로젝트로 웹 사이트를 하나 만들고 있던 와중에 방 정리를 하는데 예전에 구매해두었던 라즈베리 파이를 발견했&quot; data-og-host=&quot;poalim.tistory.com&quot; data-og-source-url=&quot;https://poalim.tistory.com/12&quot; data-og-url=&quot;https://poalim.tistory.com/12&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bmHvBW/hyH1Yltaru/tkbSOIge5ZJeSUjtzukg3k/img.png?width=452&amp;amp;height=40&amp;amp;face=0_0_452_40,https://scrap.kakaocdn.net/dn/bAC6zu/hyH3zdiu6q/9nyOkAN9GBVfqK6xC2sZmk/img.png?width=452&amp;amp;height=40&amp;amp;face=0_0_452_40,https://scrap.kakaocdn.net/dn/k5tZU/hyH12IaSJ6/btk9b1KMTdtqm3MNOcyY10/img.png?width=1232&amp;amp;height=334&amp;amp;face=0_0_1232_334&quot;&gt;&lt;a href=&quot;https://poalim.tistory.com/12&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://poalim.tistory.com/12&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bmHvBW/hyH1Yltaru/tkbSOIge5ZJeSUjtzukg3k/img.png?width=452&amp;amp;height=40&amp;amp;face=0_0_452_40,https://scrap.kakaocdn.net/dn/bAC6zu/hyH3zdiu6q/9nyOkAN9GBVfqK6xC2sZmk/img.png?width=452&amp;amp;height=40&amp;amp;face=0_0_452_40,https://scrap.kakaocdn.net/dn/k5tZU/hyH12IaSJ6/btk9b1KMTdtqm3MNOcyY10/img.png?width=1232&amp;amp;height=334&amp;amp;face=0_0_1232_334');&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;안녕하세요, 오늘 다를 주제는 라즈베리 파이에 관한 주제입니다. 요즘 사이드프로젝트로 웹 사이트를 하나 만들고 있던 와중에 방 정리를 하는데 예전에 구매해두었던 라즈베리 파이를 발견했&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;poalim.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포트포워딩을 통해 ssh 통신으로 외부에서 작업을 할 수도 있고, 웹 서버에 외부에서 접속할 수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포트포워딩을 할 때 외부포트 번호를 80 으로 주면, TCP/UDP 의 HTTP 통신을 위한 기본 포트가 80 번 이기 때문에 뒤에 포트 번호를 붙이지 않고 ip 주소만으로 접속할 수 있습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제 프로젝트를 기준으로 로컬에서 작업할 때 http://localhost:3000 으로 접속하였고, 3000포트를 사용하므로 포트포워딩 규칙에 외부포트 80, 내부포트 3000 으로 설정하면 글로벌IP 만으로 라즈베리 파이에서 동작하는 사이트에 접속할 수 있습니다.&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;5. IP 고정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라즈베리 파이는 기본적으로 DHCP 를 통해 동적으로 IP 를 할당받습니다. 제 경우에는 집에서 사용하는 공유기 네트워크에 라즈베리 파이를 연결해두었기 때문에 이 라즈베리 파이의 IP 를 고정해주어야 할 필요가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라즈베리 파이가 연결된 iptime 공유기에 접속해서 라즈베리파이의 내부 IP 를 고정IP 로 할당되도록 해주어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 각 공유기 별 관리자 페이지에 접속합니다. iptime 은 192.168.0.1 이며, 라즈베리 파이에서 ip route 명령어를 입력하면 default via 다음에 나오는 주소로 접속하면 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-filename=&quot;스크린샷 2020-10-30 오전 10.48.54.png&quot; data-origin-width=&quot;442&quot; data-origin-height=&quot;76&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TpDcq/btqMaFPrywa/oXxDOVPTczN3l1xrVWD8N1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TpDcq/btqMaFPrywa/oXxDOVPTczN3l1xrVWD8N1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TpDcq/btqMaFPrywa/oXxDOVPTczN3l1xrVWD8N1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTpDcq%2FbtqMaFPrywa%2FoXxDOVPTczN3l1xrVWD8N1%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;442&quot; height=&quot;76&quot; data-filename=&quot;스크린샷 2020-10-30 오전 10.48.54.png&quot; data-origin-width=&quot;442&quot; data-origin-height=&quot;76&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공유기 설정에 들어갔으면 관리도구 -&amp;gt; 고급설정 -&amp;gt; 네트워크 관리 -&amp;gt; 내부 네트워크 관리 로 들어가시면 현재 공유기에 연결되어 있는 기기들의 목록이 나오고, 맨 밑에 수동 주소 입력이라는 창이 있습니다. 여기에 라즈베리 파이의 맥주소와 할당할 IP 를 입력하면 고정 IP 를 할당할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iptime 외의 다른 공유기들도 메뉴는 다 비슷하니 잘 찾아보시면 될 거에요 :)&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 alignLeft&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-filename=&quot;스크린샷 2020-10-30 오전 10.52.19.png&quot; data-origin-width=&quot;752&quot; data-origin-height=&quot;42&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BNkhV/btqMaEJPQcN/RNsVPlOVLCp8EgDRZ5a5tK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BNkhV/btqMaEJPQcN/RNsVPlOVLCp8EgDRZ5a5tK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BNkhV/btqMaEJPQcN/RNsVPlOVLCp8EgDRZ5a5tK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBNkhV%2FbtqMaEJPQcN%2FRNsVPlOVLCp8EgDRZ5a5tK%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;752&quot; height=&quot;42&quot; data-filename=&quot;스크린샷 2020-10-30 오전 10.52.19.png&quot; data-origin-width=&quot;752&quot; data-origin-height=&quot;42&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 있는 부분에 앞에 4칸짜리는 할당할 IP 주소를 적는 부분입니다. 내부 IP 주소로 고정하고 싶은 IP 를 적어주시면 되고, 뒤에 6칸짜리는 라즈베리파이의 맥주소를 적어주시면 됩니다. 맥주소는 라즈베리 파이에서 ifconfig 명령어로 확인할 수 있는데요, ifconfig 명령어 입력 시 inet xxx.xxx.xxx.xxx 이렇게 나오는 부분이 현재 IP 이고, 그 밑에쯤에 ether xx:xx:xx:xx:xx:xx 이런식으로 있는 주소가 맥주소입니다.&lt;/p&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;이제 내부 IP 를 고정했으니 공유기의 외부 IP 를 통해 언제나 라즈베리 파이 서버에 접속하실 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서, 한 가지 사항이 더 있는데 외부 IP 의 문제인데 일반적으로 회사가 아닌 일반 가정에서는 IP 를 보안이나 IP 부족 문제로 DHCP 를 통한 동적 할당을 받습니다. 물론 고정 IP 를 ISP로부터 구입할 수는 있지만 요금이 붙어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가 만든 것은 라즈베리 파이를 사용한 간단한 홈서버이기 때문에 고정 IP 를 구매하지는 않았습니다. 원래 DHCP 를 통한 IP 의 동적할당은 기기의 전원이 꺼지거나 유효기간이 만료되었을 때 IP 가 새로 할당되지만, 가정에서 사용하는 공유기의 특성상 한 번 전원을 연결하면 잘 건드리지 않죠! 따라서 정전이나, 특이한 사항이 없는 경우 공유기는 IP 가 새로 할당되지 않습니다. 그래서 현재 공유기에 할당된 IP 를 사용하기로 했어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;완전한 고정 IP 는 아니지만 어떻게 보면 반고정 IP 로 사용할 수 있겠네요.&lt;/p&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;/p&gt;</description>
      <category>Programming/Server</category>
      <category>개발자</category>
      <category>라즈베리파이</category>
      <category>사이드프로젝트</category>
      <category>포트포워딩</category>
      <category>홈서버</category>
      <author>aa_rong_blog</author>
      <guid isPermaLink="true">https://poalim.tistory.com/13</guid>
      <comments>https://poalim.tistory.com/13#entry13comment</comments>
      <pubDate>Fri, 30 Oct 2020 11:04:41 +0900</pubDate>
    </item>
    <item>
      <title>[포트포워딩] 라즈베리파이 외부접속하기</title>
      <link>https://poalim.tistory.com/12</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;그 때 이번에 만드는 사이트는 이 라즈베리파이를 서버로 구동해보면 재밌겠다 라는 생각을 했습니다.&lt;/p&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;h4 data-ke-size=&quot;size20&quot;&gt;1. 같은 네트워크에서 접속하기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 기본적으로 라즈베리파이와 같은 네트워크에 연결되어 있을 때는 단순 아이피 주소와 ssh 를 사용하여 접속할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ifconfig 를 사용하여 라즈베리 파이의 내부 ip 주소를 알 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-filename=&quot;스크린샷 2020-10-24 오후 3.39.52.png&quot; data-origin-width=&quot;452&quot; data-origin-height=&quot;40&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpEvDW/btqLDN96GCa/G9b7du9JnuOH2IKUQDqd30/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpEvDW/btqLDN96GCa/G9b7du9JnuOH2IKUQDqd30/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpEvDW/btqLDN96GCa/G9b7du9JnuOH2IKUQDqd30/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbpEvDW%2FbtqLDN96GCa%2FG9b7du9JnuOH2IKUQDqd30%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;452&quot; height=&quot;40&quot; data-filename=&quot;스크린샷 2020-10-24 오후 3.39.52.png&quot; data-origin-width=&quot;452&quot; data-origin-height=&quot;40&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 inet 이라고 적혀있는 뒷부분에 IPv4 아이피 주소가 192.168.xx.xx 이런식으로 적혀있을 거에요. 이 주소를 터미널을 여시고&lt;/p&gt;
&lt;pre id=&quot;code_1603521694375&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ssh pi@192.168.xx.xx&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;(저는 맥북을 사용하며 이 포스트는 MacOS 기준으로 맞추어져 있습니다. 윈도우의 경우 putty 를 이용해서 접속할 수 있습니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pi 는 라즈베리파이의 기본 userID 이고 @ 뒤에는 라즈베리파이에 할당된 아이피 주소를 적어주면 됩니다.&lt;/p&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;h4 data-ke-size=&quot;size20&quot;&gt;2. 공인IP &amp;amp;&amp;amp; 사설IP&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에 ifconfig 에서 얻은 ip 정보는 사설ip 로 외부에서 여기에 접속하기 위해서는 공인ip 를 통한 접속이 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 공인ip, 사설ip 에 관해 간단히 설명하자면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공인IP (Public IP) 란 ISP 가 제공하는 &lt;b&gt;전 세계에서 유일하며 외부에 공개되어 있는&lt;/b&gt; IP 주소입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사설IP (Private IP) 란 일반 가정이나 회사 등에서 하나의 공인IP 안에서 공유기 등의 라우터를 통해 할당되는 IP 주소입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 하나의 공인IP 안에서 다시 여러 개의 사설IP 가 존재하여 공인IP 만으로 여러 IP 를 사용할 수 있도록 한 것이고, 이는 IPv4 의 주소부족으로 인해 고안된 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주로 가정에서 사용하는 사설 IP 는 192.168.xx.xx(class C) 로 시작이 고정되어 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. 외부에서 라즈베리파이 접속하기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 공인IP, 사설IP 의 문제로 1번에서 확인한 192.168.xx.xx 의 주소로 외부에서 접속한다고 하면 내가 원하는 라즈베리파이가 아닌 다른 곳으로 연결을 시도할 것입니다. 이는 포트포워딩이라는 과정을 통해 해결할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포트포워딩이란 하나의 IP 와 포트번호가 결합된 요청을 다른곳으로 넘겨주는 네트워크 주소 변환의 일종입니다. 이를 사용하여 공유기의 공인IP 와 설정해준 임의의 포트번호의 결합을 내부의 라즈베리파이의 사설IP 로 보내주도록 포트포워딩 설정을 해 주면 외부에서 라즈베리파이에 접속할 수 있게 됩니다.&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라즈베리 파이에서 ip route 명령어를 입력하면 라우터 정보가 나옵니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-filename=&quot;스크린샷 2020-10-24 오후 4.53.23.png&quot; data-origin-width=&quot;1010&quot; data-origin-height=&quot;72&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cj6fEE/btqLEksTDkx/Q2pE96jfcy4SKKgs3kRJ1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cj6fEE/btqLEksTDkx/Q2pE96jfcy4SKKgs3kRJ1k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cj6fEE/btqLEksTDkx/Q2pE96jfcy4SKKgs3kRJ1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcj6fEE%2FbtqLEksTDkx%2FQ2pE96jfcy4SKKgs3kRJ1k%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;1010&quot; height=&quot;72&quot; data-filename=&quot;스크린샷 2020-10-24 오후 4.53.23.png&quot; data-origin-width=&quot;1010&quot; data-origin-height=&quot;72&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제 경우에는 iptime 공유기를 사용하고 있고 default via 뒤에 있는 192.168.0.1 주소를 인터넷에 주소창에 입력하면 iptime 공유기 관리자 창에 접속할 수 있습니다. (혹은 인터넷 검색을 통해 iptime 관리자 ip 등으로 검색하시면 sk, kt, lg, iptime 등 공유기에 맞는 관리자 접속 주소를 알 수 있습니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관리자 페이지에 접속하시면 아이디와 비밀번호를 치도록 나와있는데, iptime 의 경우에는 기본 id : admin, 비밀번호 : admin 으로 설정되어 있고 이를 통해 들어가신 후 아이디, 비밀번호를 재설정 할 수 있습니다. skt 공유기의 경우는 기본 id : admin, 비밀번호 : &lt;span style=&quot;color: #ee2323;&quot;&gt;유선MAC 번호 뒤 6자리&lt;/span&gt;_admin 으로 알고 있는데 유선MAC 번호는 공유기 아래부분에 붙어 있는 스티커에 적혀 있습니다. 공유기 기본 id, password 는 검색을 통하여 알 수 있으니 찾아서 들어가시면 됩니다.&lt;/p&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;gt;외부IP주소 라고 나와있는데 이 주소가 외부에서 공유기로 접속할 수 있는 공인IP 주소이고, 기억해두시면 됩니다.&lt;/p&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;gt;NAT/라우터 관리-&amp;gt;포트포워드 설정 에 있는데 iptime 외에 다른 공유기들도 비슷한 이름으로 되어 있을 거에요!&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;widthContent&quot; data-filename=&quot;스크린샷 2020-10-24 오후 5.02.57.png&quot; data-origin-width=&quot;882&quot; data-origin-height=&quot;240&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/boHzKn/btqLElyxNgH/6payoKzujmi6ZYwx9qQzn1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/boHzKn/btqLElyxNgH/6payoKzujmi6ZYwx9qQzn1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/boHzKn/btqLElyxNgH/6payoKzujmi6ZYwx9qQzn1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FboHzKn%2FbtqLElyxNgH%2F6payoKzujmi6ZYwx9qQzn1%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;882&quot; height=&quot;240&quot; data-filename=&quot;스크린샷 2020-10-24 오후 5.02.57.png&quot; data-origin-width=&quot;882&quot; data-origin-height=&quot;240&quot;/&gt;&lt;/span&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;규칙이름에는 이 규칙을 식별할 수 있도록 임의의 이름을 입력해주시면 되고, 내부 IP 주소에는 아까 라즈베리 파이에서 ifconfig 해서 얻은 주소를 입력해주시면 됩니다. 제 경우에는 192.168.0.14네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;외부 포트는 아까 말씀드렸던 공인IP + 포트 조합에서 포트 부분에 해당하는 것이고 임의의 포트를 선택해주시면 됩니다. 제 경우에는 8000 번으로 설정했고, 내부 포트는 라즈베리파이에 접속하는 포트를 말하는데 저는 ssh 를 이용하여 라즈베리 파이에 접속할 것이어서 22를 입력해줬습니다. 참고로 ssh 접속 기본 포트가 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;설정을 완료해 준 후 '추가' 를 누른 후 맨 위 오른쪽에 있는 '저장' 도 꼭 눌러주셔야 합니다. 이렇게 완료가 되면 이제 외부에서 공인IP 와 포트 8000번으로 라즈베리 파이에 ssh 연결을 할 수 있습니다.&lt;/p&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 id=&quot;code_1603527027529&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ssh pi@xxx.xxx.xxx.xxx -p [포트번호]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 식으로 -p 옵션을 주면 됩니다. xxx.xxx.xxx.xxx 부분에는 아까 공유기 설정에서 기억해두라고 했던 외부IP주소 를 입력하시면 되고 [v포트번호] 는 포트포워드 규칙을 추가할 때 설정해준 외부포트를 적으시면 됩니다.&lt;/p&gt;

&lt;h4 data-ke-size=&quot;size20&quot;&gt;4. 공유기를 2중으로 사용하는 경우&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우는 흔하지 않은데, 저같은 경우에 집에는 skt 공유기로 인터넷을 사용하고 제 방에 이 skt 공유기에서 연결한 iptime 공유기를 하나 더 연결해서 사용하고 있습니다. 라즈베리파이는 이 iptime 공유기에 연결되어 있구요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 그래서 외부에서 라즈베리파이에 접속을 하기 위해서는 포트포워딩을 두 번 해 주었는데요, 우선 skt 공유기의 관리자 설정에서 하나의 포트포워딩 규칙을 만들어서 iptime 공유기의 주소로 접속할 수 있게 해주었습니다. 이 경우에 skt 공유기 설정에서 규칙에 추가해주는 내부IP주소가 iptime 공유기에서 확인한 외부IP주소가 됩니다. 외부 포트는 임의로 주고, 이 경우에는 내부 포트 번호도 임의로 설정해 주었습니다. 이 내부포트가 iptime 의 공유기에 접근하는 입장에서는 외부 포트번호가 되겠죠!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;임의의 포트를 주고 규칙을 저장한 후에 이번에는 iptime 의 공유기 설정에서 새로운 포트포워드 규칙을 만들고 내부 ip 주소는 라즈베리파이의 주소, 외부포트는 skt 공유기의 포트포워드 규칙에서 입력해준 내부포트 번호를 넣고, 내부 포트는 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;접속할 때는 아까와 동일하게&lt;/p&gt;
&lt;pre id=&quot;code_1603527548248&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ssh pi@xxx.xxx.xxx.xxx -p [포트번호]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때, 주의할 점은 xxx.xxx.xxx.xxx 부분은 제일 상위의 공유기 외부ip주소, 즉 제 입장에서는 skt 공유기에서 확인한 외부ip주소를 넣어주어야 하고, 포트번호도 해당 공유기에서 포트포워딩 규칙에 넣어준 외부포트 번호를 넣어주어야 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-filename=&quot;스크린샷 2020-10-24 오후 5.18.30.png&quot; data-origin-width=&quot;1232&quot; data-origin-height=&quot;334&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0EjnA/btqLIGH1lw8/k3bUbSK0K9dFzHSeDfPcT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0EjnA/btqLIGH1lw8/k3bUbSK0K9dFzHSeDfPcT1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0EjnA/btqLIGH1lw8/k3bUbSK0K9dFzHSeDfPcT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0EjnA%2FbtqLIGH1lw8%2Fk3bUbSK0K9dFzHSeDfPcT1%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;1232&quot; height=&quot;334&quot; data-filename=&quot;스크린샷 2020-10-24 오후 5.18.30.png&quot; data-origin-width=&quot;1232&quot; data-origin-height=&quot;334&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연결에 성공하면 위와 같은 메세지가 뜨고, 라즈베리파이에 접속이 됩니다 :)&lt;/p&gt;</description>
      <category>Programming/Server</category>
      <author>aa_rong_blog</author>
      <guid isPermaLink="true">https://poalim.tistory.com/12</guid>
      <comments>https://poalim.tistory.com/12#entry12comment</comments>
      <pubDate>Sat, 24 Oct 2020 17:23:12 +0900</pubDate>
    </item>
    <item>
      <title>[C++ STL] 리스트(List)</title>
      <link>https://poalim.tistory.com/9</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;STL 은 C++ 표준 템플릿 라이브러리이고, 일반적으로 가장 많이 사용되는 라이브러리는 컨테이너 라이브러리 입니다. 컨테이너는 말 그대로 자료 형들을 담는 역할을 하고 STL 컨테이너는 list, vector, deque, map, set 이 있습니다. 이 포스트에서는 list 에 대해서 다뤄보겠습니다.&lt;/p&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;List 의 자료구조는 연결 리스트로 되어 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;List 는 앞뒤로 모두 이동이 가능한 이중연결 리스트로 이루어져 있습니다. 연결 리스트는 일반적인 배열과는 다르게 조금 복잡한 모양과 구현 과정을 가지는데요, 이러한 자료구조를 사용함으로써 얻는 몇 가지 이득이 있습니다.&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;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;데이터의 삽입, 삭제가 용이하다.
&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;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 장점도 있지만 단점도 분명히 존재하는데요, 연결 리스트는 그 구조의 특성상 중간의 데이터에 접근할 때 랜덤 접근이 불가하고 순차 접근만 가능합니다. 연결 리스트의 head 에서부터 시작하여 해당 노드를 찾아주어야 하기 때문에 &lt;b&gt;검색 및 접근 속도는 배열에 비해 느려집니다.&amp;nbsp;&lt;/b&gt;그렇기 때문에 List 를 사용하기 전에 사용하고자 하는 프로그램의 특성을 잘 따져보아야 합니다. 저장할 데이터의 개수가 가변적이고, 데이터의 삽입과 삭제가 자주 일어나며, 데이터에 랜덤하게 접근하는 경우가 많지 않은 경우에 사용하기에 적합합니다.&lt;/p&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;List 의 사용방법&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;List 를 사용하기 위해서는 list 헤더파일을 포함해주어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;#include &amp;lt;list&amp;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; namespace 를 선언해주거나, STL 라이브러리 앞에 namespace 를 적어주어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;using namespace std; // namespace 선언&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;std::list&amp;lt;int&amp;gt; myList; // namespace 선언 없이 사용할 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 한 가지 특징으로 List 에는 &lt;b&gt;반복자(iterator)&lt;/b&gt; 라는 개념이 있습니다. 연결 리스트를 탐색할 때 사용하는 포인터와 비슷한 개념으로 List 에 저장된 데이터에 접근하기 위하여 사용되며 컨테이너에서 특정 위치를 가리키며 포인터와 비슷하게 ++ 와 -- 와 이동합니다. 반복자는 아래와 같이 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;list&amp;lt;int&amp;gt;::iterator firstIterator = myList.begin();&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 begin() 멤버함수는 해당 List 의 첫번째 요소를 가리키는 반복자를 반환합니다. 이와 비슷하게 end() 는 List 의 맨 마지막의 다음 요소를 가리키고, 역 방향으로 rbegin(), rend() 도 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1596876523569&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 반복자의 사용을 보여주는 예시
list&amp;lt;int&amp;gt; myList;
for(list&amp;lt;int&amp;gt;::iterator iterPos = myList.begin(); iterPos != myList.end(); iterPos++){
    cout&amp;lt;&amp;lt;*iterPos&amp;lt;&amp;lt;endl;
}&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;List 의 주요 멤버들&lt;/b&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%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 17.9071%;&quot;&gt;멤버&lt;/td&gt;
&lt;td style=&quot;width: 82.0929%;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 17.9071%;&quot;&gt;begin&lt;/td&gt;
&lt;td style=&quot;width: 82.0929%;&quot;&gt;컨테이너의 첫 번째 요소&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 17.9071%;&quot;&gt;end&lt;/td&gt;
&lt;td style=&quot;width: 82.0929%;&quot;&gt;컨테이너의 마지막의 다음 요소&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 17.9071%;&quot;&gt;rbegin&lt;/td&gt;
&lt;td style=&quot;width: 82.0929%;&quot;&gt;역순으로 첫 번째 요소&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 17.9071%;&quot;&gt;rend&lt;/td&gt;
&lt;td style=&quot;width: 82.0929%;&quot;&gt;역순으로 마지막의 다음 요소&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 17.9071%;&quot;&gt;push_front&lt;/td&gt;
&lt;td style=&quot;width: 82.0929%;&quot;&gt;맨 앞에 데이터 추가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 17.9071%;&quot;&gt;push_back&lt;/td&gt;
&lt;td style=&quot;width: 82.0929%;&quot;&gt;맨 뒤에 데이터 추가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 17.9071%;&quot;&gt;pop_front&lt;/td&gt;
&lt;td style=&quot;width: 82.0929%;&quot;&gt;맨 앞에서 데이터 하나 삭제&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 17.9071%;&quot;&gt;pop_back&lt;/td&gt;
&lt;td style=&quot;width: 82.0929%;&quot;&gt;맨 뒤에서 데이터 하나 삭제&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 17.9071%;&quot;&gt;front&lt;/td&gt;
&lt;td style=&quot;width: 82.0929%;&quot;&gt;맨 앞의 데이터의 참조 반환&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 17.9071%;&quot;&gt;back&lt;/td&gt;
&lt;td style=&quot;width: 82.0929%;&quot;&gt;마지막 데이터의 참조 반환&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 17.9071%;&quot;&gt;clear&lt;/td&gt;
&lt;td style=&quot;width: 82.0929%;&quot;&gt;컨테이너의 모든 데이터 삭제&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 17.9071%;&quot;&gt;empty&lt;/td&gt;
&lt;td style=&quot;width: 82.0929%;&quot;&gt;컨테이너가 저장하고 있는 데이터가 없으면 true, 아니면 false 반환&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 17.9071%;&quot;&gt;size&lt;/td&gt;
&lt;td style=&quot;width: 82.0929%;&quot;&gt;컨테이너가 저장하고 있는 요소의 개수 반환&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 17.9071%;&quot;&gt;insert&lt;/td&gt;
&lt;td style=&quot;width: 82.0929%;&quot;&gt;특정 위치에 데이터 삽입&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 17.9071%;&quot;&gt;erase&lt;/td&gt;
&lt;td style=&quot;width: 82.0929%;&quot;&gt;지정한 범위의 데이터 삭제&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 17.9071%;&quot;&gt;remove&lt;/td&gt;
&lt;td style=&quot;width: 82.0929%;&quot;&gt;지정한 값과 일치하는 모든 데이터 삭제&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 17.9071%;&quot;&gt;remove_if&lt;/td&gt;
&lt;td style=&quot;width: 82.0929%;&quot;&gt;조건을 만족하는 모든 데이터 삭제&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 17.9071%;&quot;&gt;sort&lt;/td&gt;
&lt;td style=&quot;width: 82.0929%;&quot;&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;위의 멤버들의 사용 방법은 아래 코드를 확인하기 바란다.&lt;/p&gt;
&lt;pre id=&quot;code_1596879638593&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;list&amp;gt;
using namespace std;

int main(){
    list&amp;lt;int&amp;gt; myList;

    myList.push_front(3); // 맨 앞에 3 추가
    myList.push_front(2); // 맨 앞에 2 추가
    myList.push_front(1); // 맨 앞에 1 추가
    myList.push_back(10); // 맨 뒤에 10 추가
    myList.push_back(11); // 맨 뒤에 11 추가
    myList.push_back(12); // 맨 뒤에 12 추가

	// iterator 사용하여 list 의 데이터 전체 출력
    for(list&amp;lt;int&amp;gt;::iterator iter = myList.begin(); iter != myList.end(); iter++){
        cout&amp;lt;&amp;lt;&quot;List 의 데이터 : &quot;&amp;lt;&amp;lt;*iter&amp;lt;&amp;lt;endl;
    }

    cout&amp;lt;&amp;lt;&quot;첫 번째 데이터 : &quot;&amp;lt;&amp;lt;myList.front()&amp;lt;&amp;lt;endl;
    cout&amp;lt;&amp;lt;&quot;마지막 데이터 : &quot;&amp;lt;&amp;lt;myList.back()&amp;lt;&amp;lt;endl;
    cout&amp;lt;&amp;lt;&quot;데이터의 개수 : &quot;&amp;lt;&amp;lt;myList.size()&amp;lt;&amp;lt;endl;

    myList.pop_front(); // 맨 앞 데이터 하나 삭제
    myList.pop_back(); // 맨 뒤 데이터 하나 삭제
    cout&amp;lt;&amp;lt;&quot;데이터의 개수 : &quot;&amp;lt;&amp;lt;myList.size()&amp;lt;&amp;lt;endl;

    myList.insert(myList.begin(), 2); // myList 의 첫번째 위치에 2 삽입
    myList.insert(myList.end(), 2, 100); // myList 의 마지막 위치에 100을 2개 삽입

    list&amp;lt;int&amp;gt;::iterator frontIter = myList.begin();
    for(int i = 0; i &amp;lt; 3; i++) frontIter++; // 4번째 요소를 가리키는 iterator 를 생성

    cout&amp;lt;&amp;lt;&quot;4번쨰부터 마지막 데이터 삭제&quot;&amp;lt;&amp;lt;endl;
    myList.erase(frontIter, myList.end());

    myList.remove(3); // list 에서 값이 3 인 요소 모두 삭제

    myList.clear(); // list 의 요소 전체 삭제
    cout&amp;lt;&amp;lt;myList.size()&amp;lt;&amp;lt;endl;
    cout&amp;lt;&amp;lt;myList.empty()&amp;lt;&amp;lt;endl;

    return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Programming/C++</category>
      <author>aa_rong_blog</author>
      <guid isPermaLink="true">https://poalim.tistory.com/9</guid>
      <comments>https://poalim.tistory.com/9#entry9comment</comments>
      <pubDate>Sat, 8 Aug 2020 18:44:18 +0900</pubDate>
    </item>
    <item>
      <title>[C++ STL] 템플릿(Template)</title>
      <link>https://poalim.tistory.com/8</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;STL 이 Standard Template Library 의 약자이며 표준 템플릿들의 모음이라는 것을 알게 되었는데 이 템플릿(Template) 이라는 것이 도대체 뭘까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'템플릿' 이라는 용어는 PPT 템플릿, 파일 템플릿 등 일상 생활에서도 많이 사용됩니다. 사전적 의미로는 '틀', '주형' 등의 의미로 무언가를 담거나 미리 정해두는 틀이라는 의미를 갖습니다. 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;pre id=&quot;code_1596607969438&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int Max(int a, int b){
    return a &amp;gt; b ? a : b;
}&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;&quot;성적은 정수형이 아니라 실수형인데 어떡하지?&quot;&lt;/p&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 id=&quot;code_1596608858254&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;float Max(float a, float b){
    return a &amp;gt; b ? a : b;
}&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;/p&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;이렇게 템플릿을 사용하여 만드는 함수를 &quot;함수 템플릿&quot; 이라고 부르는데, 정의하는 방법은 아래와 같습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;template &amp;lt;typename T&amp;gt; function&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수 선언 부분은 데이터 타입을 T 로 바꾸고 일반 함수의 선언처럼 하시면 됩니다. 그러면 위의 2개의 max 함수를 템플릿을 사용하여 하나의 함수로 만들어 보겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1596610087985&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;template &amp;lt;typename T&amp;gt;
T Max(T a, T b){
    return a &amp;gt; b ? a : b;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;int, float 등의 데이터 형이 들어가던 자리를 T라는 임의의 타입으로 두고 템플릿 함수를 만들어 봤습니다. 한 번 테스트를 해볼까요?&lt;/p&gt;
&lt;pre id=&quot;code_1596611384308&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
using namespace std;

template &amp;lt;typename T&amp;gt;
T Max(T a, T b){
    return a &amp;gt; b ? a : b;
}

int main(){
    int sec1 = 1;
    int sec2 = 2;
    cout&amp;lt;&amp;lt;Max(sec1, sec2)&amp;lt;&amp;lt;endl;
    
    float score1 = 97.5;
    float score2 = 50.8;
    cout&amp;lt;&amp;lt;Max(score1, score2)&amp;lt;&amp;lt;endl;
    return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과는 잘 나오는 것 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-filename=&quot;스크린샷 2020-08-05 오후 4.10.21.png&quot; data-origin-width=&quot;214&quot; data-origin-height=&quot;78&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blQxkU/btqGjeX1Ruc/B5yGakKyBG2LlSvHNtoEyk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blQxkU/btqGjeX1Ruc/B5yGakKyBG2LlSvHNtoEyk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blQxkU/btqGjeX1Ruc/B5yGakKyBG2LlSvHNtoEyk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblQxkU%2FbtqGjeX1Ruc%2FB5yGakKyBG2LlSvHNtoEyk%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;214&quot; height=&quot;78&quot; data-filename=&quot;스크린샷 2020-08-05 오후 4.10.21.png&quot; data-origin-width=&quot;214&quot; data-origin-height=&quot;78&quot;/&gt;&lt;/span&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;pre id=&quot;code_1596612047978&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;template &amp;lt;typename T1, typename T2&amp;gt;
T1 multiply(T1 a, T2 b){
    return a * b;
}&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;&amp;nbsp;&lt;/p&gt;</description>
      <category>Programming/C++</category>
      <author>aa_rong_blog</author>
      <guid isPermaLink="true">https://poalim.tistory.com/8</guid>
      <comments>https://poalim.tistory.com/8#entry8comment</comments>
      <pubDate>Wed, 5 Aug 2020 16:23:31 +0900</pubDate>
    </item>
    <item>
      <title>[알고리즘] 재귀(Recursion) - 하노이탑</title>
      <link>https://poalim.tistory.com/6</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;하노이의 탑은 3개의 기둥과 서로 크기가 다른 여러개의 원판들이 있을 때 이 원판들을 한 기둥으로부터 다른 기둥으로 옮기는 일종의 게임입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;게임을 시작하기 전 초기 상태는 3개의 기둥 중 하나의 기둥에 모든 원판들이 아래에서부터 크기가 큰 순으로 놓여져 있으며, 최종 목표는 이 원판들을 하나씩 옮겨서 순서 그대로 다른 기둥에 쌓는 것입니다. 이 때 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;/li&gt;
&lt;li&gt;쌓아 놓은 원판은 항상 위의 것이 아래의 것보다 작아야 한다.&lt;/li&gt;
&lt;/ol&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, 2, 3번 기둥이 있고, n 개의 서로 다른 크기의 원판들이 있다고 할 때 1번 기둥에 쌓여져 있는 n 개의 원판들을 모두 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;3번 기둥에 순서대로 원판들을 쌓기 위해서는 가장 큰 원판을 1번 기둥의 맨 아래에서 3번 기둥의 맨 아래로 이동시켜야 합니다. 그러기 위해서는 가장 큰 기둥 위에 쌓여져 있는 n-1 개의 원판들을 임시로 2번으로 옮긴 후 가장 큰 원판을 3번으로 옮기고 나서, 2번에 쌓아둔 n-1 개의 원판들을 다시 3번의 가장 큰 원판 위에 차례대로 쌓으면 됩니다. 이 과정을 재귀를 사용하여 표현하면,&lt;/p&gt;
&lt;pre id=&quot;code_1596528978989&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Hanoi(int n, int from, int to){
    Hanoi(n-1, from, 6-from-to);
    printf(&quot;%d -&amp;gt; %d&quot;, from, to);
    Hanoi(n-1, 6-from-to, to);
}&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;이렇게 재귀 함수를 구성하고, 함수가 멈추는 base case 를 설정하여 주면,&lt;/p&gt;
&lt;pre id=&quot;code_1596528999198&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Hanoi(int n, int from, int to){
    if(n == 0) return ;
    Hanoi(n-1, from, 6-from-to);
    printf(&quot;%d -&amp;gt; %d&quot;, from, to);
    Hanoi(n-1, 6-from-to, to);
}&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더불어, 하노이탑을 해결하는 원판의 최소 이동횟수도 재귀함수를 사용하여 구할 수 있는데, 위 함수를 조금 변형하여서 해결할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1596529013666&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int Hanoi(int n, int from, int to){
    mid = 6 - from - to;
    if(n == 1) return 1;
    else return Hanoi(n-1, from, mid) + 1 + Hanoi(n-1, mid, from);
}&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 함수를 만들고 실행하여 보면, 하노이탑의 최소 이동횟수가 1, 3, 7, 15 ... 이렇게 익숙한 수가 나오는 것을 볼 수 있습니다.&lt;br /&gt;하노이탑의 최소 이동횟수를 나타내는 함수를 f(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) = 2f(n-1) + 1$&lt;br /&gt;$f(n) + 1 = 2(f(n-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;이렇게 표현이 가능하고, f(n) + 1 을 g(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;$g(n) = 2g(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;이렇게 등비수열로 표현이 되고, 이를 일반항으로 표현하면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$g(n) = 2^n$&lt;br /&gt;$f(n) = 2^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;이 된다는 것을 알 수 있습니다.&lt;/p&gt;</description>
      <category>Programming/Algorithms</category>
      <author>aa_rong_blog</author>
      <guid isPermaLink="true">https://poalim.tistory.com/6</guid>
      <comments>https://poalim.tistory.com/6#entry6comment</comments>
      <pubDate>Tue, 4 Aug 2020 17:02:09 +0900</pubDate>
    </item>
  </channel>
</rss>