<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>좋아하는 일 그리고 잘하는 일, 그 사이 어딘가</title>
    <link>https://kangworld.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Fri, 17 Apr 2026 03:23:52 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>Kangworld</managingEditor>
    <image>
      <title>좋아하는 일 그리고 잘하는 일, 그 사이 어딘가</title>
      <url>https://tistory1.daumcdn.net/tistory/2871878/attach/b8b1676e3d15480cad3592b923ef8f19</url>
      <link>https://kangworld.tistory.com</link>
    </image>
    <item>
      <title>[2026.01.19] 만약에 우리</title>
      <link>https://kangworld.tistory.com/349</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;만약에우리.png&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1536&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwuMjg/dJMcaiPzOYh/6t6I1457DFkYTQB9N5TrM1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwuMjg/dJMcaiPzOYh/6t6I1457DFkYTQB9N5TrM1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwuMjg/dJMcaiPzOYh/6t6I1457DFkYTQB9N5TrM1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwuMjg%2FdJMcaiPzOYh%2F6t6I1457DFkYTQB9N5TrM1%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;400&quot; height=&quot;600&quot; data-filename=&quot;만약에우리.png&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1536&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;소원 같은 건 진작 잊어버렸다.&lt;/i&gt;&lt;i&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;기억하는 건&lt;/i&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;같이 소원을 빌었다는 것&lt;/i&gt;&lt;i&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;우리가 있었다는 것.&lt;/i&gt;&lt;/p&gt;</description>
      <category>일상</category>
      <author>Kangworld</author>
      <guid isPermaLink="true">https://kangworld.tistory.com/349</guid>
      <comments>https://kangworld.tistory.com/349#entry349comment</comments>
      <pubDate>Mon, 19 Jan 2026 21:37:59 +0900</pubDate>
    </item>
    <item>
      <title>[웹툰] 가담항설 리뷰</title>
      <link>https://kangworld.tistory.com/348</link>
      <description>&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;580&quot; data-origin-height=&quot;837&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnBW11/dJMcahb3LRe/Jfx3527hPXILCiVsStzwbK/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnBW11/dJMcahb3LRe/Jfx3527hPXILCiVsStzwbK/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnBW11/dJMcahb3LRe/Jfx3527hPXILCiVsStzwbK/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbnBW11%2FdJMcahb3LRe%2FJfx3527hPXILCiVsStzwbK%2Fimg.webp&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;451&quot; height=&quot;651&quot; data-origin-width=&quot;580&quot; data-origin-height=&quot;837&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: center;&quot;&gt;&amp;nbsp;&lt;br&gt;&lt;b&gt;랑또 작가의 장편 웹툰&lt;/b&gt;&lt;br&gt;&lt;b&gt;가담항설&lt;/b&gt;&lt;br&gt;&amp;nbsp;&lt;br&gt;2025 연말에 정주행한 기념으로&lt;br&gt;짧게나마 리뷰를 남겨본다.&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot;&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: center;&quot;&gt;&amp;nbsp;&lt;br&gt;가담항설은 아주아주 먼 옛날 이야기이다.&lt;br&gt;한반도 역사로 따지면 초기 조선시대쯤?&lt;br&gt;농업 중심의 한 국가가 있었다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;이 나라에는 아주 큰 근심거리가 있었는데&lt;br&gt;가뭄, 홍수, 천둥번개, 태풍, 해일 등&lt;br&gt;천재지변이 끊이질 않았다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;왕은 날씨를 다스리던 용을 찾아가 제안을 한다.&lt;br&gt;&quot;당신을 위해 1000년을 기도해서&lt;br&gt;인간의 모습과 불노불사의 능력을 만들어 줄 테니&lt;br&gt;자연을 이롭게 다스려 달라.&quot;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;729&quot; data-origin-height=&quot;696&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/v0m8l/dJMcacaK9Jc/ogNdoUpHJ4cJcOqVEcn4i1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/v0m8l/dJMcacaK9Jc/ogNdoUpHJ4cJcOqVEcn4i1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/v0m8l/dJMcacaK9Jc/ogNdoUpHJ4cJcOqVEcn4i1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fv0m8l%2FdJMcacaK9Jc%2FogNdoUpHJ4cJcOqVEcn4i1%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;400&quot; height=&quot;382&quot; data-origin-width=&quot;729&quot; data-origin-height=&quot;696&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: center;&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;용은 이 제안을 받아들이고&lt;br&gt;1000년이라는 긴 시간 동안 잠에 빠지게 된다.&lt;br&gt;이때 용은 전국에서 가장 빼어난 사군자를 곁에 두고 잠에 드는데&lt;br&gt;훗날 이는 용을 보필하는 네 명의 호위무사가 된다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;1000년 동안 하루도 빠짐없이 기도를 받았으면 좋았겠지만...&lt;br&gt;날짜를 세는 관료의 실수로 하루 일찍 깨어나면서&lt;br&gt;불노의 몸은 얻었지만 불사의 몸은 아닌&lt;br&gt;역린을 지닌 존재가 된다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;용의 등장으로 나라는 눈에 띄게 안정되었으나&lt;br&gt;그의 등장으로 입지가 좁아진 세력이 용을 암살하려 했고&lt;br&gt;그 과정에서 용이 가장 아끼던 사군자 춘매가 목숨을 잃는다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;이 사건 이후 용은 더 이상&lt;br&gt;인간을 믿지 않았고&lt;br&gt;날씨를 이롭게 다스리지 않았으며&lt;br&gt;공포로 나라를 다스리는&lt;br&gt;이른바 폭군이 되어버리고 만다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;그러던 어느 날,&lt;br&gt;한 신하가 춘매를 살릴 수 있다며 &lt;b&gt;천동지&lt;/b&gt;라는 물건을 언급한다.&lt;br&gt;천동지는 가담항설에서 정말 정말 중요하게 다뤄지는 물건으로&lt;br&gt;깊은 염원을 담아 적으면 소원이 이뤄지는 아주 아주 신비한 종이다.&lt;br&gt;만화 드래곤볼로 치면 드래곤볼쯤 되겠다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;천동지는 매년 한 마을에서 조공으로 바쳐온 귀중한 물건으로&lt;br&gt;춘매를 살리기 위해선 여러 장의 천동지가 필요했고&lt;br&gt;마지막 하나의 천동지만을 남겨둔 상태에서&lt;br&gt;이를 징수하기 위해 관군으로 둔갑한 용이&lt;br&gt;마을에 잠입하는 것으로 가담항설 1화가 시작된다.&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;718&quot; data-origin-height=&quot;560&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TlYb0/dJMb99ZpVmO/rkkUbT3g0YKjgdkS6sb6cK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TlYb0/dJMb99ZpVmO/rkkUbT3g0YKjgdkS6sb6cK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TlYb0/dJMb99ZpVmO/rkkUbT3g0YKjgdkS6sb6cK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTlYb0%2FdJMb99ZpVmO%2FrkkUbT3g0YKjgdkS6sb6cK%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;400&quot; height=&quot;312&quot; data-origin-width=&quot;718&quot; data-origin-height=&quot;560&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot;&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: center;&quot;&gt;&amp;nbsp;&lt;br&gt;세계관도 너무 매력적이지만&lt;br&gt;캐릭터들의 대사 하나하나&lt;br&gt;울림이 있어 여운이 남는다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;676&quot; data-origin-height=&quot;813&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/76nmp/dJMcad1KNki/kIiYxVSoL1PpRtNdykdtYK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/76nmp/dJMcad1KNki/kIiYxVSoL1PpRtNdykdtYK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/76nmp/dJMcad1KNki/kIiYxVSoL1PpRtNdykdtYK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F76nmp%2FdJMcad1KNki%2FkIiYxVSoL1PpRtNdykdtYK%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;400&quot; height=&quot;481&quot; data-origin-width=&quot;676&quot; data-origin-height=&quot;813&quot;/&gt;&lt;/span&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; style=&quot;text-align: center;&quot;&gt;개추!&lt;/p&gt;</description>
      <category>만화</category>
      <author>Kangworld</author>
      <guid isPermaLink="true">https://kangworld.tistory.com/348</guid>
      <comments>https://kangworld.tistory.com/348#entry348comment</comments>
      <pubDate>Mon, 12 Jan 2026 23:40:28 +0900</pubDate>
    </item>
    <item>
      <title>[2026.01.01] 소 잃고 뇌 약간 고침</title>
      <link>https://kangworld.tistory.com/346</link>
      <description>&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2121&quot; data-origin-height=&quot;3769&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oPIuS/dJMcabbMHRG/UeSou5gsTKhykjEe6inaMK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oPIuS/dJMcabbMHRG/UeSou5gsTKhykjEe6inaMK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oPIuS/dJMcabbMHRG/UeSou5gsTKhykjEe6inaMK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoPIuS%2FdJMcabbMHRG%2FUeSou5gsTKhykjEe6inaMK%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;296&quot; height=&quot;526&quot; data-origin-width=&quot;2121&quot; data-origin-height=&quot;3769&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: center;&quot;&gt;&amp;nbsp;&lt;br&gt;&lt;i&gt;2026.01.01 목요일&lt;/i&gt;&lt;br&gt;&amp;nbsp;&lt;br&gt;누군가의 버팀목이 된다는 것이&lt;br&gt;매우 지치고 괴로운 일이었다.&lt;br&gt;&lt;br&gt;돌이켜보니 &lt;br&gt;내가 누군가의 버텀목이란 사실이 &lt;br&gt;나를 지탱하고 있었음을.&lt;br&gt;&lt;br&gt;그때의 난 몰랐다.&lt;/p&gt;</description>
      <category>일상</category>
      <author>Kangworld</author>
      <guid isPermaLink="true">https://kangworld.tistory.com/346</guid>
      <comments>https://kangworld.tistory.com/346#entry346comment</comments>
      <pubDate>Thu, 1 Jan 2026 23:53:57 +0900</pubDate>
    </item>
    <item>
      <title>[2024.08.11] 혼자 떠난 일본 여행 (2)</title>
      <link>https://kangworld.tistory.com/341</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;2024.07.24 수요일&lt;/i&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_20240811_121854098_01.jpg&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;3024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CqaiB/btsI0GYFHSd/TZkbrFVY8K5zGHpKjg1X6K/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CqaiB/btsI0GYFHSd/TZkbrFVY8K5zGHpKjg1X6K/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CqaiB/btsI0GYFHSd/TZkbrFVY8K5zGHpKjg1X6K/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCqaiB%2FbtsI0GYFHSd%2FTZkbrFVY8K5zGHpKjg1X6K%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;600&quot; height=&quot;600&quot; data-filename=&quot;KakaoTalk_20240811_121854098_01.jpg&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;3024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;아침 일찍 달려간 금각사&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;사찰 전체가 빤짝빤짝 빛난다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;금빛 전각을 제외하고 크게 볼 게 없었던&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_20240811_121854098_03.jpg&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;3024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b4R42x/btsI1nYlbKx/pPFQUriJy0ENKnnzGmVv40/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b4R42x/btsI1nYlbKx/pPFQUriJy0ENKnnzGmVv40/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b4R42x/btsI1nYlbKx/pPFQUriJy0ENKnnzGmVv40/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb4R42x%2FbtsI1nYlbKx%2FpPFQUriJy0ENKnnzGmVv40%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;600&quot; height=&quot;600&quot; data-filename=&quot;KakaoTalk_20240811_121854098_03.jpg&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;3024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;금각사에서 내려와 &lt;span style=&quot;letter-spacing: 0px;&quot;&gt;대나무숲으로 가기 위해 버스를 기다리던 중 비가 내리기 시작&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;비가 내린 이후 온도가 내려가면서 교토 여행이 (너무 * 100) 즐겁기 시작&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_20240811_121854098_04.jpg&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;3024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/boA2cP/btsI0BiIcdF/IxxWtSWQh6eng83thP77Pk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/boA2cP/btsI0BiIcdF/IxxWtSWQh6eng83thP77Pk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/boA2cP/btsI0BiIcdF/IxxWtSWQh6eng83thP77Pk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FboA2cP%2FbtsI0BiIcdF%2FIxxWtSWQh6eng83thP77Pk%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;600&quot; height=&quot;600&quot; data-filename=&quot;KakaoTalk_20240811_121854098_04.jpg&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;3024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;비 오는 풍경이 너무 좋아서&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_20240811_121854098_06.jpg&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;3024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c142JL/btsI0RyRkyZ/YYc26VeLSguQxxg4f8Mmj0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c142JL/btsI0RyRkyZ/YYc26VeLSguQxxg4f8Mmj0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c142JL/btsI0RyRkyZ/YYc26VeLSguQxxg4f8Mmj0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc142JL%2FbtsI0RyRkyZ%2FYYc26VeLSguQxxg4f8Mmj0%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;600&quot; height=&quot;600&quot; data-filename=&quot;KakaoTalk_20240811_121854098_06.jpg&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;3024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;지하철에서 내리니 비가 그쳤다&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_20240811_121854098_07.jpg&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;3024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGSycO/btsIZQAZHHN/BcSWj8nKK8cosmKHUnziB0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGSycO/btsIZQAZHHN/BcSWj8nKK8cosmKHUnziB0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGSycO/btsIZQAZHHN/BcSWj8nKK8cosmKHUnziB0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGSycO%2FbtsIZQAZHHN%2FBcSWj8nKK8cosmKHUnziB0%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;600&quot; height=&quot;600&quot; data-filename=&quot;KakaoTalk_20240811_121854098_07.jpg&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;3024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;대나무숲 입장&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_20240805_214404190_18.jpg&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;3024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDANnB/btsI0SEyR4B/xJGCgpc4PWTopK7BEBDKk0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDANnB/btsI0SEyR4B/xJGCgpc4PWTopK7BEBDKk0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDANnB/btsI0SEyR4B/xJGCgpc4PWTopK7BEBDKk0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDANnB%2FbtsI0SEyR4B%2FxJGCgpc4PWTopK7BEBDKk0%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;600&quot; height=&quot;600&quot; data-filename=&quot;KakaoTalk_20240805_214404190_18.jpg&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;3024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;대나무인 척&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_20240805_214404190_20.jpg&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WMC9I/btsIZ8nNZey/DHp8EYvkBNphkdou7iWg4k/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WMC9I/btsIZ8nNZey/DHp8EYvkBNphkdou7iWg4k/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WMC9I/btsIZ8nNZey/DHp8EYvkBNphkdou7iWg4k/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWMC9I%2FbtsIZ8nNZey%2FDHp8EYvkBNphkdou7iWg4k%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;600&quot; height=&quot;800&quot; data-filename=&quot;KakaoTalk_20240805_214404190_20.jpg&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;대나무가 바람에 흔들리는 소리, 새가 지저귀는 소리.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;는 사진으로 못 담지만 대충 좋았다는 의미&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_20240811_121854098_08.jpg&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LmKTB/btsI0w27GNq/30XUyfngirVHyqk5qZt5n0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LmKTB/btsI0w27GNq/30XUyfngirVHyqk5qZt5n0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LmKTB/btsI0w27GNq/30XUyfngirVHyqk5qZt5n0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLmKTB%2FbtsI0w27GNq%2F30XUyfngirVHyqk5qZt5n0%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;600&quot; height=&quot;800&quot; data-filename=&quot;KakaoTalk_20240811_121854098_08.jpg&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;여행에서 제일 좋았던 가츠라강&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;한 30분 정도 강을 바라보며 앉아있었다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;크게 무슨 생각을 하진 않았고 평온한 느낌에 교토에 오길 잘 했다는 생각이 들었다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_20240811_121854098_10.jpg&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;3024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4LEgm/btsI1DmkByS/xk9vX02CoI1ww0KdRg85y0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4LEgm/btsI1DmkByS/xk9vX02CoI1ww0KdRg85y0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4LEgm/btsI1DmkByS/xk9vX02CoI1ww0KdRg85y0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4LEgm%2FbtsI1DmkByS%2Fxk9vX02CoI1ww0KdRg85y0%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;600&quot; height=&quot;600&quot; data-filename=&quot;KakaoTalk_20240811_121854098_10.jpg&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;3024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;강원도 살 때 이런 느낌의 강에서 가족들과 종종 낚시를 하곤 했다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;또다시 가족과 고향 생각에 울컥했던&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_20240811_121854098_12.jpg&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;3024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dY5u48/btsI079koGK/P0cMcgZqa5nFxyOKj2kjn0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dY5u48/btsI079koGK/P0cMcgZqa5nFxyOKj2kjn0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dY5u48/btsI079koGK/P0cMcgZqa5nFxyOKj2kjn0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdY5u48%2FbtsI079koGK%2FP0cMcgZqa5nFxyOKj2kjn0%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;600&quot; height=&quot;600&quot; data-filename=&quot;KakaoTalk_20240811_121854098_12.jpg&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;3024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;새 들은 무슨 생각 하며 살까?&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_20240811_121854098_15.jpg&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;3024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwjiqc/btsI0LMgigg/9iwxkd54ZDdCqlEYGTFdwk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwjiqc/btsI0LMgigg/9iwxkd54ZDdCqlEYGTFdwk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwjiqc/btsI0LMgigg/9iwxkd54ZDdCqlEYGTFdwk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbwjiqc%2FbtsI0LMgigg%2F9iwxkd54ZDdCqlEYGTFdwk%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;600&quot; height=&quot;600&quot; data-filename=&quot;KakaoTalk_20240811_121854098_15.jpg&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;3024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;북적이는 걸 피해서 일부러 사람 없는 민가를 가로질러 버스 타러 갔다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이날도 진짜 더웠는데 민가에서 마주친 어떤 아주머니가 &quot;아츠이네~ 아츠이 아츠이&quot; 말을 걸어오셨다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;뭔가 덥다 의미 같았는데 춥다는 의미의 사무이밖에 몰라서 멋쩍은 웃음만 지으며 호다닥 도망치듯 걸어갔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_20240811_121854098_19.jpg&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;3024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/s907J/btsI0HwuoJZ/tVIW9ogMmkjyTwVicNGaNk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/s907J/btsI0HwuoJZ/tVIW9ogMmkjyTwVicNGaNk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/s907J/btsI0HwuoJZ/tVIW9ogMmkjyTwVicNGaNk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fs907J%2FbtsI0HwuoJZ%2FtVIW9ogMmkjyTwVicNGaNk%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;600&quot; height=&quot;450&quot; data-filename=&quot;KakaoTalk_20240811_121854098_19.jpg&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;3024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;노부부가 운영하는 소바 집&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;대표 메뉴인 텐뿌라 주문&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;한국에서 먹는 소바와 다른 점은 찍어 먹는 쯔유에 있는 것 같다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;정말 달고, 짜고, 가쓰오부시 향이 그득한 메밀면 찍어 먹기 최적의 소스&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_20240811_121854098_21.jpg&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;3024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blrGfc/btsI0HJZVi0/KJdPw4jW85xR8cMsX8h5MK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blrGfc/btsI0HJZVi0/KJdPw4jW85xR8cMsX8h5MK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blrGfc/btsI0HJZVi0/KJdPw4jW85xR8cMsX8h5MK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblrGfc%2FbtsI0HJZVi0%2FKJdPw4jW85xR8cMsX8h5MK%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;600&quot; height=&quot;450&quot; data-filename=&quot;KakaoTalk_20240811_121854098_21.jpg&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;3024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;숙소에서 좀 쉬다가 도착한 카모강&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_20240811_121854098_24.jpg&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;3024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9P4oh/btsI0IPtkmR/ADG9fY8K7RmWWoKi2j4tk1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9P4oh/btsI0IPtkmR/ADG9fY8K7RmWWoKi2j4tk1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9P4oh/btsI0IPtkmR/ADG9fY8K7RmWWoKi2j4tk1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9P4oh%2FbtsI0IPtkmR%2FADG9fY8K7RmWWoKi2j4tk1%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;600&quot; height=&quot;600&quot; data-filename=&quot;KakaoTalk_20240811_121854098_24.jpg&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;3024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;낮에 비가 와서 그런지 강이 엄청 불어있었다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_20240805_214404190_27.jpg&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dnt4aL/btsI02AaL59/3tpm1paueZFz0N9nrTefL1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dnt4aL/btsI02AaL59/3tpm1paueZFz0N9nrTefL1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dnt4aL/btsI02AaL59/3tpm1paueZFz0N9nrTefL1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdnt4aL%2FbtsI02AaL59%2F3tpm1paueZFz0N9nrTefL1%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;500&quot; height=&quot;667&quot; data-filename=&quot;KakaoTalk_20240805_214404190_27.jpg&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;맥주 한 캔 사들고 강가에 앉아 홀짝홀짝 마셨다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;강을 바라보며 생각을 정리한 것 같은데 구체적으로 뭘 생각했는지 기억은 안 나는..&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;아마 회사 생활을 비롯한 20대 직장인의 흔한 고민이 아니였을지&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_20240811_121854098_25.jpg&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;2544&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b22M0e/btsIZZdBeoy/wD2wKcEgyCzAkplZcKKNkk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b22M0e/btsIZZdBeoy/wD2wKcEgyCzAkplZcKKNkk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b22M0e/btsIZZdBeoy/wD2wKcEgyCzAkplZcKKNkk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb22M0e%2FbtsIZZdBeoy%2FwD2wKcEgyCzAkplZcKKNkk%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;600&quot; height=&quot;505&quot; data-filename=&quot;KakaoTalk_20240811_121854098_25.jpg&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;2544&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;저녁 늦게 도착한 이자카야&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;첫 주문으로 가라아게&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;약간 오버 쿠킹됐다;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;맛은 슴슴했다. 난 짭짤한 가라아게가 좋다고!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_20240811_121854098_26.jpg&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;3024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bAFQfY/btsIZ5YZrwW/opLTgxEVroYx3QvKE2k0H1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bAFQfY/btsIZ5YZrwW/opLTgxEVroYx3QvKE2k0H1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bAFQfY/btsIZ5YZrwW/opLTgxEVroYx3QvKE2k0H1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbAFQfY%2FbtsIZ5YZrwW%2FopLTgxEVroYx3QvKE2k0H1%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;600&quot; height=&quot;600&quot; data-filename=&quot;KakaoTalk_20240811_121854098_26.jpg&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;3024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;정말 맛있게 먹었던 숙성 사시미 오마카세&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;최고는 오징어&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;찰기가 어마어마해서 저 초록 잎에서 잘 떨어지지도 않았다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;씹으면 쫀득한 오징어가 잇몸 사이사이를 밀고 들어온다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;오징어 특유의 단맛은 말해 뭐해&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_20240811_144209200.jpg&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;3024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCTA8y/btsIZ5EHOeD/SGEOgJM7aen3Ncl6otk6c0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCTA8y/btsIZ5EHOeD/SGEOgJM7aen3Ncl6otk6c0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCTA8y/btsIZ5EHOeD/SGEOgJM7aen3Ncl6otk6c0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCTA8y%2FbtsIZ5EHOeD%2FSGEOgJM7aen3Ncl6otk6c0%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;600&quot; height=&quot;600&quot; data-filename=&quot;KakaoTalk_20240811_144209200.jpg&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;3024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;메뉴 명은 horse&lt;span style=&quot;background-color: #ffffff; color: #4d5156; text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;mackerel 구이&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #4d5156; text-align: left;&quot;&gt;horse가 붙어있길래 말고기인줄 알았다 ㅋㅋ;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #4d5156; text-align: left;&quot;&gt;한국말로 하면 전갱이 구이&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #4d5156; text-align: left;&quot;&gt;딱 쥐포 맛&lt;/span&gt;&lt;/p&gt;</description>
      <category>일상</category>
      <author>Kangworld</author>
      <guid isPermaLink="true">https://kangworld.tistory.com/341</guid>
      <comments>https://kangworld.tistory.com/341#entry341comment</comments>
      <pubDate>Sun, 11 Aug 2024 14:44:55 +0900</pubDate>
    </item>
    <item>
      <title>[2024.08.05] 상반기 일상 &amp;amp; 혼자 떠난 일본 여행 (1)</title>
      <link>https://kangworld.tistory.com/340</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1440&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KAvlm/btsIUku24Qu/abCvg2F8Jw1EDVCQxMeYcK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KAvlm/btsIUku24Qu/abCvg2F8Jw1EDVCQxMeYcK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KAvlm/btsIUku24Qu/abCvg2F8Jw1EDVCQxMeYcK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKAvlm%2FbtsIUku24Qu%2FabCvg2F8Jw1EDVCQxMeYcK%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;500&quot; height=&quot;667&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1440&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;2024.01.09 화요일&lt;/i&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #3a4954; text-align: center;&quot;&gt;강릉 버드나무 브루어리&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #3a4954; text-align: center;&quot;&gt;겨울 굴은 항상 옳다&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;3024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dlPf8e/btsIUDujhak/fEe0ZI4QalB3PbX6soitZ0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dlPf8e/btsIUDujhak/fEe0ZI4QalB3PbX6soitZ0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dlPf8e/btsIUDujhak/fEe0ZI4QalB3PbX6soitZ0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdlPf8e%2FbtsIUDujhak%2FfEe0ZI4QalB3PbX6soitZ0%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;500&quot; height=&quot;375&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;3024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;2024.04.09 화요일&lt;/i&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #3a4954;&quot;&gt;인천 랜더스 필드 &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #3a4954;&quot;&gt;존맛 크림 새우&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/P4wvK/btsIVGYdck2/vklG816enpFjmZKqtUokk0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/P4wvK/btsIVGYdck2/vklG816enpFjmZKqtUokk0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/P4wvK/btsIVGYdck2/vklG816enpFjmZKqtUokk0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FP4wvK%2FbtsIVGYdck2%2FvklG816enpFjmZKqtUokk0%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;500&quot; height=&quot;667&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;째정&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;80세까지 계속 야구해줘...&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;3024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sRcPA/btsIWg5JPLk/8A2b4lsDgLq01pGEkeqJG1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sRcPA/btsIWg5JPLk/8A2b4lsDgLq01pGEkeqJG1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sRcPA/btsIWg5JPLk/8A2b4lsDgLq01pGEkeqJG1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsRcPA%2FbtsIWg5JPLk%2F8A2b4lsDgLq01pGEkeqJG1%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;500&quot; height=&quot;375&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;3024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;2024.04.16 화요일&lt;/i&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;회사 근처 볼링장&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;비 정기적 볼링 동호회 모임&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;첫 모임에 비하면 장족의 발전&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cyvwVN/btsIUeuLEUZ/ABHVrEGxKVae6uAVPBJlVK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cyvwVN/btsIUeuLEUZ/ABHVrEGxKVae6uAVPBJlVK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cyvwVN/btsIUeuLEUZ/ABHVrEGxKVae6uAVPBJlVK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcyvwVN%2FbtsIUeuLEUZ%2FABHVrEGxKVae6uAVPBJlVK%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;500&quot; height=&quot;667&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;2024.05.31 금요일&lt;/i&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;고척 스카이돔&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;선발 오원석 매우 호투했던 걸로 기억&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;3024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cZVZVj/btsIWxsIKp8/FW7WHZjr3P4XxO5Y5J2Wx0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cZVZVj/btsIWxsIKp8/FW7WHZjr3P4XxO5Y5J2Wx0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cZVZVj/btsIWxsIKp8/FW7WHZjr3P4XxO5Y5J2Wx0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcZVZVj%2FbtsIWxsIKp8%2FFW7WHZjr3P4XxO5Y5J2Wx0%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;600&quot; height=&quot;600&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;3024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;2024.07.23 화요일&lt;/i&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;대망의 일본 여행&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;교토 2박 3일, 오사카 2박 3일&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;3024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lq32P/btsIWbDyYxI/Hq9tJ2lXBJMz3EqhWkQ92K/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lq32P/btsIWbDyYxI/Hq9tJ2lXBJMz3EqhWkQ92K/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lq32P/btsIWbDyYxI/Hq9tJ2lXBJMz3EqhWkQ92K/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Flq32P%2FbtsIWbDyYxI%2FHq9tJ2lXBJMz3EqhWkQ92K%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;600&quot; height=&quot;600&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;3024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;간사이 국제공항에서 교토로 넘어가기 위해 탄 JR 하루카&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;3024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dyPJ3K/btsIWfMCQNM/35Mq3ek50CvtWENI6uWCQ1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dyPJ3K/btsIWfMCQNM/35Mq3ek50CvtWENI6uWCQ1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dyPJ3K/btsIWfMCQNM/35Mq3ek50CvtWENI6uWCQ1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdyPJ3K%2FbtsIWfMCQNM%2F35Mq3ek50CvtWENI6uWCQ1%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;600&quot; height=&quot;600&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;3024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;바보처럼 왕복 티켓의 출발일을 두 개 다 여행 출발일로 발권했다;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;다행히 교토역 안내 센터에서 날짜를 바꿀 수 있었다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;영어 가능한 직원분들이 계셔서 천만다행&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;3024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tygn3/btsIVdhD4tY/FifnQLXXKOiZ1JMXenp4g1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tygn3/btsIVdhD4tY/FifnQLXXKOiZ1JMXenp4g1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tygn3/btsIVdhD4tY/FifnQLXXKOiZ1JMXenp4g1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ftygn3%2FbtsIVdhD4tY%2FFifnQLXXKOiZ1JMXenp4g1%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;600&quot; height=&quot;600&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;3024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;기차에서 마신 녹차&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;오&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;타&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;니&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;3024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bv6Hgl/btsIWubHoit/QubVvhHLIpfZlYaq1KHifK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bv6Hgl/btsIWubHoit/QubVvhHLIpfZlYaq1KHifK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bv6Hgl/btsIWubHoit/QubVvhHLIpfZlYaq1KHifK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbv6Hgl%2FbtsIWubHoit%2FQubVvhHLIpfZlYaq1KHifK%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;600&quot; height=&quot;600&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;3024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;날씨가 정말 좋았던 교토&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;날씨가 너무 좋은 나머지 체감 온도 38도를 가볍게 찍는다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;사우나 불가마 들어갔을 때 숨이 턱 막히는 것처럼&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;역에서 내리면 잠시 숨이 안 쉬어진다;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;3024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0ceAT/btsIWj9e4RB/fdiDhroWdxanaks0Mhl8U1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0ceAT/btsIWj9e4RB/fdiDhroWdxanaks0Mhl8U1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0ceAT/btsIWj9e4RB/fdiDhroWdxanaks0Mhl8U1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0ceAT%2FbtsIWj9e4RB%2FfdiDhroWdxanaks0Mhl8U1%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;600&quot; height=&quot;600&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;3024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;숙소에 짐 맡기고 바로 달려갔던 우나기동 맛집.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;너무 맛있었다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;3024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MKJ39/btsIUNjfKW1/KKv47r9gwIzz6oqYu4r05k/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MKJ39/btsIUNjfKW1/KKv47r9gwIzz6oqYu4r05k/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MKJ39/btsIUNjfKW1/KKv47r9gwIzz6oqYu4r05k/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMKJ39%2FbtsIUNjfKW1%2FKKv47r9gwIzz6oqYu4r05k%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;600&quot; height=&quot;600&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;3024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;점심먹고 도착한 청수사&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;3024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dpoW3P/btsIViDbUAr/F5vZN7Tknzgs9jYR2wtuu1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dpoW3P/btsIViDbUAr/F5vZN7Tknzgs9jYR2wtuu1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dpoW3P/btsIViDbUAr/F5vZN7Tknzgs9jYR2wtuu1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdpoW3P%2FbtsIViDbUAr%2FF5vZN7Tknzgs9jYR2wtuu1%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;600&quot; height=&quot;600&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;3024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;한국과 비슷하면서도 다른 느낌&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;교토 특유의 고즈넉함이 느껴져서 정말 좋았다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;3024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRIEcy/btsIVdaUAAV/4byRry6RWEM5rw4AapVGw0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRIEcy/btsIVdaUAAV/4byRry6RWEM5rw4AapVGw0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRIEcy/btsIVdaUAAV/4byRry6RWEM5rw4AapVGw0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRIEcy%2FbtsIVdaUAAV%2F4byRry6RWEM5rw4AapVGw0%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;600&quot; height=&quot;600&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;3024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;청수사 메인 스팟에서 바라본 교토 시내&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;교토 타워가 보인다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;여행 중 알게 된 사실인데, 쿄토는 교토 타워보다 높은 건물은 짓지 못한다고 한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;엄밀히 말하면 교토 타워보다는 아닌데 대충 뭐 그렇다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cfyF5O/btsIUzeoiET/DWiFnm8CogaoRmICeXSZlK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cfyF5O/btsIUzeoiET/DWiFnm8CogaoRmICeXSZlK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cfyF5O/btsIUzeoiET/DWiFnm8CogaoRmICeXSZlK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcfyF5O%2FbtsIUzeoiET%2FDWiFnm8CogaoRmICeXSZlK%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;600&quot; height=&quot;800&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;청수사에서 내려오며 찍은 가옥&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;역사와 전통이 있어 보여서 찍었다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lC0Lu/btsIWflwAiP/GGVY6r1U7bcw35gEtLrIrK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lC0Lu/btsIWflwAiP/GGVY6r1U7bcw35gEtLrIrK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lC0Lu/btsIWflwAiP/GGVY6r1U7bcw35gEtLrIrK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlC0Lu%2FbtsIWflwAiP%2FGGVY6r1U7bcw35gEtLrIrK%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;600&quot; height=&quot;800&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;저녁 가까이 도착한 철학의 길&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;아주 작은 하천과 돌길인데 사람도 없고 너무 평온했다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;어렸을적에 이런 곳에서 자주 놀곤 했는데&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 그런지 고향 생각에 울컥했던&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;3024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mqxJZ/btsIUzk5IR3/EZ5bYsSAPpafqAU4Pavt2K/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mqxJZ/btsIUzk5IR3/EZ5bYsSAPpafqAU4Pavt2K/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mqxJZ/btsIUzk5IR3/EZ5bYsSAPpafqAU4Pavt2K/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmqxJZ%2FbtsIUzk5IR3%2FEZ5bYsSAPpafqAU4Pavt2K%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;600&quot; height=&quot;600&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;3024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;늦은 저녁 도착했던 이자카야에서의 첫 안주, 호르몬 폰즈&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;대창과 유자 소스 아는 맛의 조합인데 너무 맛있었다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;3024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c2IJed/btsIWhXS2xt/vBonKKWviBu0WvGEfkVeh1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c2IJed/btsIWhXS2xt/vBonKKWviBu0WvGEfkVeh1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c2IJed/btsIWhXS2xt/vBonKKWviBu0WvGEfkVeh1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc2IJed%2FbtsIWhXS2xt%2FvBonKKWviBu0WvGEfkVeh1%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;600&quot; height=&quot;600&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;3024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;기분이 너무 좋아버린 나머지 대충 찍어버린 맥주&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그 이후의 안주와 술은 이성을 잃고 사진 찍는 걸 까먹었다.&lt;/p&gt;</description>
      <category>일상</category>
      <author>Kangworld</author>
      <guid isPermaLink="true">https://kangworld.tistory.com/340</guid>
      <comments>https://kangworld.tistory.com/340#entry340comment</comments>
      <pubDate>Mon, 5 Aug 2024 23:06:10 +0900</pubDate>
    </item>
    <item>
      <title>[Redis] Sorted Set : 기본 명령어</title>
      <link>https://kangworld.tistory.com/337</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;202809_redis_icon.png&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;512&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kYEPW/btsGSQiStu9/WUTN4XKApGLKUhKKsaYFP1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kYEPW/btsGSQiStu9/WUTN4XKApGLKUhKKsaYFP1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kYEPW/btsGSQiStu9/WUTN4XKApGLKUhKKsaYFP1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkYEPW%2FbtsGSQiStu9%2FWUTN4XKApGLKUhKKsaYFP1%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;300&quot; height=&quot;300&quot; data-filename=&quot;202809_redis_icon.png&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;512&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Sorted&amp;nbsp;Set&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Redis의 Sorted Set은 말 그대로 &lt;b&gt;정렬된 집합&lt;/b&gt;을 표현하는 자료구조로, 각 요소는 &lt;b&gt;고유한&lt;/b&gt;&amp;nbsp;&lt;b&gt;멤버&lt;/b&gt;와 &lt;b&gt;스코어&lt;/b&gt; 한 쌍으로 이루어진다. 스코어는 멤버의 순서를 결정하는 데 사용된다. Sorted Set의 대표적인 기능으로 &lt;b&gt;멤버의 추가, 삭제, 검색&lt;/b&gt;을 제공하며, &lt;b&gt;스코어를 기반으로 한 업데이트, 조회&lt;/b&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;Sorted Set의 특성은 다음과 같다 :&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. &lt;b&gt;고유한 멤버 : 멤버는 고유한 값으로 구성된다.&lt;/b&gt; &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. &lt;b&gt;정렬된 상태로 저장 : 스코어에 따라 정렬되어 저장된다.&lt;/b&gt; &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 빠른 조회 : 내부적으로 skip list로 구현되어 있어 조회 시 O(log(N))의 시간 복잡도를 갖는다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. 유연한 조회 : 스코어 기준으로도 멤버를&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp;조회 가능하다.&lt;/span&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;Sorted Set의 이러한 특성은 랭킹 시스템의 리더보드, 우선순위 큐, 세컨더리 인덱싱 구현에 유용하게 사용된다.&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: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt; &lt;span style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot;&gt;다음은 Redis에서 Sorted Set을 사용하는 몇 가지 기본적인 명령어와 그 예시이다.&lt;/span&gt;&lt;br /&gt;&lt;a href=&quot;https://redis.io/docs/latest/commands/zadd/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식 사이트&lt;/a&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;h3 data-ke-size=&quot;size23&quot;&gt;ZADD&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멤버 추가&lt;/p&gt;
&lt;pre id=&quot;code_1713872709378&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ZADD key score member [score member ...]

ZADD myzset 1 helloworld&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;ZREM&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멤버 제거&lt;/p&gt;
&lt;pre id=&quot;code_1713872801959&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ZREM key member [member ...]

ZREM myzset helloworld&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;ZSCORE&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멤버 스코어 반환&lt;/p&gt;
&lt;pre id=&quot;code_1713872809751&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ZSCORE key member

ZSCORE myzset helloworld&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;ZINCRBY&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스코어 증가. 멤버가 없다면 생성 (increase or &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;insert&lt;/span&gt;)&lt;/p&gt;
&lt;pre id=&quot;code_1713872837180&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ZINCRBY key increment member

ZINCRBY myzset 100 helloworld&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;ZCOUNT&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주어진 스코어 범위 내의 멤버 수 반환&lt;/p&gt;
&lt;pre id=&quot;code_1713963926986&quot; class=&quot;java&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;ZCOUNT key min max

ZCOUNT myzset 100 200&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;다음과 같이 스코어와 +inf를 사용하면 스코어 100의 내림차순 등수를 계산할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1713964182500&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ZINCRBY myzset 100 +inf&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;ZRANK&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멤버 순위 계산. 낮은 스코어부터 0번 순위 부여하며 동점시 멤버를 사전순으로 다시 정렬해서 순위를 부여한다.&lt;/p&gt;
&lt;pre id=&quot;code_1713963933524&quot; class=&quot;java&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ZRANK key member

ZRANK myzset helloworld&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;ZREVRANK&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멤버 역순위 계산. 높은 스코어부터 0번 순위 부여&lt;/p&gt;
&lt;pre id=&quot;code_1713963934344&quot; class=&quot;java&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ZREVRANK key member

ZREVRANK myzset helloworld&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;ZRANGE&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지정된 범위 값으로 멤버를 검색한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 순위 기반 검색이며 BYSCORE 옵션을 명시하면 스코어 기반으로 검색한다.&lt;/p&gt;
&lt;pre id=&quot;code_1713964393412&quot; class=&quot;java&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;ZRANGE key start stop [BYSCORE | BYLEX] [REV] [LIMIT offset count]   [WITHSCORES]

ZRANGE myzset 0 1
ZRANGE myzset 90 100 BYSCORE&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;ZREVRANGE&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지정된 범위 값으로 멤버를 역순으로 조회한다. ZRANGE의 반대 개념으로 ZRANGE에 REV 옵션을 명시하는 것과 동일하다. 6.2.0부터 Deprecated 되었으니 가급적 ZRANGE를 사용하자.&lt;/p&gt;
&lt;pre id=&quot;code_1713964394166&quot; class=&quot;java&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;ZREVRANGE key start stop [WITHSCORES]

ZREVRANGE myzset helloworld 0 1&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;ZRANGEBYLEX&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멤버의 사전식 순서를 기준으로 특정 문자열 범위 내에 있는 멤버를 조회한다. ZRANGE의 BYLEX 옵션을 명시하는 것과 동일하다. &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;6.2.0부터 Deprecated 되었으며 &lt;/span&gt;주의 사항으로 모든 멤버가 같은 스코어를 가질 때 유효하다.&lt;/p&gt;
&lt;pre id=&quot;code_1713968069983&quot; class=&quot;java&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ZRANGEBYLEX key min max [LIMIT offset count]

ZADD myzset 0 a 0 b 0 c 0 d 0 e 0 f 0 g
ZRANGEBYLEX myzset [a [b
1) &quot;a&quot;
2) &quot;b&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Redis</category>
      <author>Kangworld</author>
      <guid isPermaLink="true">https://kangworld.tistory.com/337</guid>
      <comments>https://kangworld.tistory.com/337#entry337comment</comments>
      <pubDate>Wed, 24 Apr 2024 23:32:11 +0900</pubDate>
    </item>
    <item>
      <title>[2024.01.01] 해피 뉴 이어~~!</title>
      <link>https://kangworld.tistory.com/332</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;093a5f72790dc9f353c242bddc349758.gif&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;270&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dUU1D0/btsCWLKKfFe/8jdHvXVkv3cXPNiX80RY51/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dUU1D0/btsCWLKKfFe/8jdHvXVkv3cXPNiX80RY51/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dUU1D0/btsCWLKKfFe/8jdHvXVkv3cXPNiX80RY51/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/dUU1D0/btsCWLKKfFe/8jdHvXVkv3cXPNiX80RY51/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;480&quot; height=&quot;270&quot; data-filename=&quot;093a5f72790dc9f353c242bddc349758.gif&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;270&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;2024.01.01 월요일&lt;/i&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;잘 가 2023, 2024도 행복한 일만 가득하길!&lt;/p&gt;</description>
      <category>일상</category>
      <author>Kangworld</author>
      <guid isPermaLink="true">https://kangworld.tistory.com/332</guid>
      <comments>https://kangworld.tistory.com/332#entry332comment</comments>
      <pubDate>Mon, 1 Jan 2024 20:48:54 +0900</pubDate>
    </item>
    <item>
      <title>[Java] 공변성으로 알아보는 제네릭과 와일드카드</title>
      <link>https://kangworld.tistory.com/327</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bweKJU/btsBurn3tfD/CKcNksY4S3RldjPdZ1gKIK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bweKJU/btsBurn3tfD/CKcNksY4S3RldjPdZ1gKIK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bweKJU/btsBurn3tfD/CKcNksY4S3RldjPdZ1gKIK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbweKJU%2FbtsBurn3tfD%2FCKcNksY4S3RldjPdZ1gKIK%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;400&quot; height=&quot;250&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&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;h1&gt;&lt;span&gt;서론&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;공변성&lt;/b&gt;과 &lt;b&gt;제네릭 &lt;/b&gt;그리고&amp;nbsp;&lt;b&gt;와일드카드&lt;/b&gt;에 대해서 알아보고 마지막으로 &lt;b&gt;@SafeVarargs&lt;/b&gt; 어노테이션을 소개로 마무리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;공변성과 배열&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;자바의 &lt;b&gt;공변성&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;/b&gt;이다. 예를 들어, 배열의 원소 타입이 상속 관계를 가질 때, 배열 자체의 타입 간에도 상속 관계가 유지된다. 간단히 말하면 하위 타입의 배열은 상위 타입의 배열로 취급될 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1701603961187&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Test
void test_array() {
    Number[] numbers = new Number[4];
    numbers[0] = 1;
    numbers[1] = 1L;
    numbers[2] = 1.0f;
    numbers[3] = 1.0d;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Number와 상속 관계에 있는 Interger, Long, Float, Double을 배열에 할당할 수 있을 뿐 아니라&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1701604038060&quot; class=&quot;angelscript&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Test
void test_array() {
    Integer[] integers = new Integer[10];
    Number[] numbers = integers;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공변성을 만족하기에 Integer[]가 Number[]로 취급될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1701604364876&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Test
void test_array() {
    Integer[] integers = new Integer[10];
    Number[] numbers = integers;

    assertThrows(ArrayStoreException.class, () -&amp;gt; numbers[0] = 1.0); // Heap pollution
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;/i&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;그런데 다음과 같이 작성된 코드는 &lt;i&gt;ArrayStoreException&lt;span&gt; &lt;/span&gt;&lt;/i&gt;예외를 발생시킨다. 그 이름에서 알 수 있듯 배열에 무언가 잘못 삽입하려고 할 때 발생하는 예외로, 선언된 타입과 할당하려는 타입이 호환되지 않을 때 발생한다. 즉, Number 배열로 참조되는 Interger 배열에 double을 삽입하면 &lt;b&gt;힙 오염(Heap pollution)&lt;/b&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;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;그런데 여기서 한 가지 재밌는 사실은 이러한 에러가 &lt;b&gt;컴파일 시점에는 감지가 안되지만 런타임 시점에는 감지&lt;/b&gt;된다는 것이다. 이는 배열이 런타임에 실제 타입 정보를 유지하기에 가능한 것으로 이를 &lt;span style=&quot;color: #343541; text-align: start;&quot;&gt;&lt;b&gt;Reifiable Type&lt;/b&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 style=&quot;color: #343541; text-align: start;&quot;&gt;Reifiable Type으로는 원시 타입(int, double, char...), 원시 타입의 배열(int[], double[], char[]...), 원시 타입 래퍼(Integer, Double...), 원시 타입 래퍼의 배열 (Integer[], Double[]...)이 있다. &lt;/span&gt;&lt;span style=&quot;color: #343541; text-align: start;&quot;&gt;반대로 컴파일 과정에서 타입 정보가 소거되는 &lt;b&gt;Non-&lt;/b&gt;&lt;span style=&quot;color: #343541; text-align: start;&quot;&gt;&lt;b&gt;Reifiable Type으로는 제네릭 타입(T, List&amp;lt;T&amp;gt;)&lt;/b&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;&amp;nbsp;&lt;/p&gt;
&lt;h1 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;제네릭의 출생&lt;/span&gt;&lt;/h1&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_1701691688268&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Test
void test_list() {
    List list = new ArrayList();
    list.add(123);
    list.add(456);
}&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;color: #333333; text-align: start;&quot;&gt;깔끔해(?) 보이지만 문제는 타입 안정성이 부족해서 코드를 잘못 작성하면 컴파일은 될지언정 런타임에 에러가 발생했다.&lt;/span&gt; &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;비단 타입 안정성만의 문제를 넘어 코드의 가독성 저하, 유지 보수의 어려움, 잦은 타입 캐스팅 문제도 존재했다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1701691888556&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; ```
    List list = new ArrayList();
    list.add(123);
    list.add(&quot;hello&quot;);
    
    for (int i = 0; i &amp;lt; list.size(); i++) {
        String element = (String) list.get(i);  // ClassCastException !!
        System.out.println(element);
    }
````&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;color: #374151; text-align: start;&quot;&gt;코드의 일반화와 재사용성 그리고 타입 캐스팅의 번거로움을 해소했다. &lt;span style=&quot;color: #374151; text-align: start;&quot;&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;&amp;nbsp;&lt;/p&gt;
&lt;h1 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;제네릭의 약점&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;마냥 좋아 보이던 제네릭도 완벽하지는 않았다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1701612323545&quot; class=&quot;java&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Test
void test_list() {
    List&amp;lt;Integer&amp;gt; integers = new ArrayList&amp;lt;&amp;gt;();
    List&amp;lt;Number&amp;gt; numbers = new ArrayList&amp;lt;&amp;gt;();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;위 코드는 &lt;b&gt;Non-&lt;/b&gt;&lt;span style=&quot;color: #343541; text-align: start;&quot;&gt;&lt;b&gt;Reifiable Type&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;규칙에 의해 컴파일 결과가 다음과 같다.&lt;/span&gt; &lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1701612502585&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Test
void test_list() {
    List integers = new ArrayList&amp;lt;&amp;gt;();
    List numbers = new ArrayList&amp;lt;&amp;gt;();
}&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;letter-spacing: 0px;&quot;&gt;여기서 제네릭 타입이 가진 약점이 발생하는데, 컴파일 과정에서 타입 정보가 사라지므로 앞선 배열의 예제처럼 런타임 시점에 힙 오염을 언어 차원에서 감지할 방법이 없다.&lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1701612059616&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Test
void test_list() {
    List&amp;lt;Integer&amp;gt; integers = new ArrayList&amp;lt;&amp;gt;(); // List integers
    List&amp;lt;Number&amp;gt; numbers = new ArrayList&amp;lt;&amp;gt;(); // List numbers

    numbers = integers; // compile error
    numbers.add(1.0); // List add 1.0 no problem !
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;만약 위 코드에서 컴파일 에러마저 없었다면 런타임 시점에 마지막 라인에서 힙 오염이 발생한다는 사실을 알 수 없다. 그렇다 컴파일 에러는 최후의 보루였던 것이다. 이러한 이유로 타입 간의 상속 관계가 배열에서 유지되지만 리스트는 유지되지 않고 컴파일 에러를 내뱉는다. &lt;b&gt;즉 배열은 공변성 반대로 리스트는 불공변성이란 사실을 알 수 있다.&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1701614348132&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Test
void print_test() {
    List&amp;lt;Integer&amp;gt; integers = Arrays.asList(1, 2, 3);
    List&amp;lt;Long&amp;gt; longs = Arrays.asList(1L, 2L, 3L);
    List&amp;lt;Float&amp;gt; floats = Arrays.asList(1.0f, 2.0f, 3.0f);
    List&amp;lt;Double&amp;gt; doubles = Arrays.asList(1.0, 2.0, 3.0);

    print(integers); // compile error
    print(longs);    // compile error
    print(floats);   // compile error
    print(doubles);  // compile error
}

private void print(List&amp;lt;Number&amp;gt; numbers) {
    numbers.forEach(System.out::println);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예제 코드에서 볼 수 있듯 리스트의 불공변성은 다형성을 활용하지 못하는 단점으로까지 이어지는데 자바는 이러한 문제를 해결하기 위해 &lt;b&gt;와일드카드&lt;/b&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;h1 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;와일드카드&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;의 등장이다. 와일드카드라는 말 그대로 어떤 타입도 될 수 있으면서 동시에 알 수 없는 타입을 표현할 때 사용한다.&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_1701694084808&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Test
void test_list() {
    List&amp;lt;Integer&amp;gt; integers = Arrays.asList(1, 2, 3);
    List&amp;lt;Long&amp;gt; longs = Arrays.asList(1L, 2L, 3L);

    print(integers);
    print(longs);
}

private void print(List&amp;lt;?&amp;gt; list) {
    list.forEach(System.out::println);
}&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_1701694200038&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private void print(List&amp;lt;?&amp;gt; list) {
    list.forEach(System.out::println); // ok !
    
    list.add(new Object());
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 제네릭과 와일드카드의 주요한 특성 중 하나인 &lt;b&gt;타입 안정성&lt;/b&gt;을 보존하는 특성에서 기인한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt; list.forEach(System.out::println);&lt;/i&gt;와 같은 읽기 연산은 해당 타입이 구체적으로 어떤 타입인지 모르지만 적어도 Object 타입으로 반환할 수 있다. 반면 &lt;i&gt;list.add(new Object());&lt;/i&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;와 같은 쓰기 작업은 해당 컬렉션에 어떤 타입이 들었는지 알 수 없기에 쓰기 연산을 제한한다. 다음 예제 코드만 봐도 와일드카드에서 쓰기 연산이 불가능한 이유를 바로 이해할 수 있을 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1701695209922&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Test
void test_list() {
    List&amp;lt;Integer&amp;gt; integers = Arrays.asList(1, 2, 3);
    List&amp;lt;?&amp;gt; wildcards = integers;

    wildcards.add(&quot;hello world!&quot;); // compile error!
}&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;h1 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;제네릭과 공변성, 반공변성&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;와일드카드의 읽기 쓰기 문제를 해결하기 위해 자바는 한 번 더 특단의 조치를 하게 되는데, 바로 &lt;b&gt;한정적 와일드카드&lt;/b&gt;의 등장이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;상한 경계 와일드카드 &amp;lt;? extends Class&amp;gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;읽기 문제부터 살펴보면, 캐스팅 안정성을 확보하려면 '최소한 어떤 타입'인지 제한할 필요가 있어 보인다. 와일드카드에 &lt;b&gt;extends&lt;/b&gt; 키워드를 추가하면 앞서 언급한 최소한의 타입을 제한할 수 있고 이를 두고&amp;nbsp;&lt;b&gt;상한 경계 와일드카드&lt;/b&gt;라고한다.&lt;/p&gt;
&lt;pre id=&quot;code_1701696604159&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Test
void test_wildcard() {
    List&amp;lt;? extends Number&amp;gt; numbers;
    numbers = new ArrayList&amp;lt;Integer&amp;gt;(); // ok!!
    numbers = new ArrayList&amp;lt;Number&amp;gt;(); // ok!!

    for (Number n : numbers) {} // ok!!
    for (Object o : numbers) {} // ok!!
    for (Integer i : numbers) {} // error!!
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;읽기는 명시된 타입과 그 부모 타입으로 가능하지만 그 외의 타입에 대해서는 실제 List&amp;lt;Double&amp;gt;을 참조할 수 있기에 Integer로 읽을 수 없고 Double도 동일한 논리로 읽기 불가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1701697256746&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Test
void test_wildcard() {
    List&amp;lt;? extends Number&amp;gt; list = new ArrayList&amp;lt;&amp;gt;();

    list.add(1); // error! could be List&amp;lt;Double&amp;gt;
    list.add(1.0); // error! could be List&amp;lt;Integer&amp;gt;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쓰기는 어떠한 타입도 불가능한데, 가령 List&amp;lt;Double&amp;gt;을 참조할 수 있기에 Integer 삽입이 불가능하고 비슷한 논리로 List&amp;lt;Integer&amp;gt;를 참조할 수 있게 Double 삽입 불가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;하한 경계 와일드 카드 &lt;b&gt;&amp;lt;? Super Class&amp;gt;&lt;/b&gt; &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순히 와일드카드를 사용 했을 때 어떠한 값도 쓰지 못했지만 &lt;b&gt;super&lt;/b&gt; 키워드를 추가하면 제한적으로 쓰기 가능한데 이를 두고 &lt;b&gt;하한 경계 와일드카드&lt;/b&gt;라고 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1701697693916&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Test
void test_wildcard() {
    List&amp;lt;? super Integer&amp;gt; list;
    list = new ArrayList&amp;lt;Integer&amp;gt;();
    list = new ArrayList&amp;lt;Number&amp;gt;();
    list = new ArrayList&amp;lt;Object&amp;gt;();

    list.add(new Integer(1));
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쓰기는 명시된 타입과 그 자식 타입으로 가능하지만 그 외의 타입에 대해서는 List&amp;lt;Integer&amp;gt;를 참조할 수 있기에 Number와 Object로는 쓰기 불가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1701697791883&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Test
void test_wildcard() {
    List&amp;lt;? super Integer&amp;gt; list = new ArrayList&amp;lt;&amp;gt;();

    Object o = list.get(0);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;읽기는 기본적으로 Object로만 읽을 수 있다. 가령 List&amp;lt;Object&amp;gt;를 참조할 수 있기에 Integer와 Number로 읽기 불가능하고 오직 Object로만 읽었을 때 안정성을 보장받을 수 있기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지 이해했다면 자연스럽게 한 가지 사실을 알 수 있는데, 바로 한정적 와일드카드의 등장으로 리스트가 공변성과 반공변성의 성질을 갖게 됐다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;@SafeVarargs&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;제네릭과 가변 인수를 함께 사용하면 컴파일러가 경고를 보내는데 이제는 그 이유를 알 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1701869790618&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public void foo(List&amp;lt;String&amp;gt;... args) {

}&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;&quot;&gt;&lt;i&gt;public void foo(List&amp;lt;String&amp;gt;... args)&lt;/i&gt;는 실제로 &lt;i&gt;public void foo(List&amp;lt;String&amp;gt;[] args)&lt;/i&gt;&lt;/span&gt;와 동일하고 Non-Reifiable Type 규칙에 따라 컴파일된 최종 형태는 &lt;i&gt;public void foo(List[] args)&lt;/i&gt;이다.&lt;/p&gt;
&lt;pre id=&quot;code_1701870964324&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private void foo(List&amp;lt;String&amp;gt;... args) {
    Object[] objects = args; // Object[] = List[]
    objects[0] = Arrays.asList(1);

    String str = args[0].get(0); // ClassCastException !!
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;컴파일된 최종 형태를 상상하며, 타입 소거와 배열의 공변성을 교묘하게 이용하면 다음과 같이 힙 오염이 가능하므로 컴파일러가 경고를 표시할 뿐만 아니라 메서드 호출 코드에도 경고 메시지가 나타난다. 이러한 상황에 메서드에 잠재적인 위험이 없다는 것을 명시적으로 알리기 위해 &lt;b&gt;@SafeVarargs&lt;/b&gt; 어노테이션을 사용한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1701872467093&quot; class=&quot;lasso&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@SafeVarargs
private void foo(List&amp;lt;String&amp;gt;... args) {
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Java 7에 최초로 도입된 당시&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp;생성자, static 메서드, final 메서드에만 허용됐는데 Java 9부터는 private 메서드에도 @SafeVarargs를 달아줄 수 있다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>Java/Java</category>
      <author>Kangworld</author>
      <guid isPermaLink="true">https://kangworld.tistory.com/327</guid>
      <comments>https://kangworld.tistory.com/327#entry327comment</comments>
      <pubDate>Wed, 6 Dec 2023 23:21:27 +0900</pubDate>
    </item>
    <item>
      <title>JMH(Java Microbenchmark Harness) 사용 예제</title>
      <link>https://kangworld.tistory.com/325</link>
      <description>&lt;div&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2048&quot; data-origin-height=&quot;2048&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dvX4rU/btsAyS6WCOV/WaOZ73fUDixb7G4He9rib0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dvX4rU/btsAyS6WCOV/WaOZ73fUDixb7G4He9rib0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dvX4rU/btsAyS6WCOV/WaOZ73fUDixb7G4He9rib0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdvX4rU%2FbtsAyS6WCOV%2FWaOZ73fUDixb7G4He9rib0%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;300&quot; height=&quot;300&quot; data-origin-width=&quot;2048&quot; data-origin-height=&quot;2048&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 style=&quot;color: #000000;&quot;&gt;&lt;span&gt;JMH(Java Microbenchmark Harness)&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JMH는 Java 언어로 작성된 코드의 성능을 측정하는 도구로, 특히 벤치 &lt;b&gt;마이크로벤치마크&lt;/b&gt;를 수행하는 데 사용한다. 마이크로벤치마크는 작은 단위의 코드에 대한 경과 시간, 명령어 처리 속도 등을 측정하는 프로그램을 의미한다. 이는 성능 최적화나 코드 변경에 대한 영향을 정량적으로 측정할 때 유용하게 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;환경 설정&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;신뢰할 수 있는 결과를 얻기 위해서는 M&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;aven을 사용해서 jar 파일로 빌드하고 이를 실행해서 테스트하길 권장하고 있다. IDE에서 실행하는 테스트는 결과의 신뢰성이 떨어진다는게 공식 문서의 입장이다.&lt;br /&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;i&gt;샘플 maven project 생성&lt;/i&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1700368210670&quot; class=&quot;bash&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;$ mvn archetype:generate -DinteractiveMode=false 
    -DarchetypeGroupId=org.openjdk.jmh -DarchetypeArtifactId=jmh-java-benchmark-archetype 
    -DgroupId=org.sample -DartifactId=test -Dversion=1.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;&lt;i&gt;혹은 maven dependency 추가&lt;/i&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1700368374999&quot; class=&quot;bash&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;org.openjdk.jmh&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;jmh-core&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;1.36&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;
&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;org.openjdk.jmh&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;jmh-generator-annprocess&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;1.36&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;&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;i&gt;jar 파일로 테스트시 plugin 추가&lt;/i&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1700372643444&quot; class=&quot;bash&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;&amp;lt;plugin&amp;gt;
    &amp;lt;groupId&amp;gt;org.apache.maven.plugins&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;maven-shade-plugin&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;2.0&amp;lt;/version&amp;gt;
    &amp;lt;executions&amp;gt;
        &amp;lt;execution&amp;gt;
            &amp;lt;phase&amp;gt;package&amp;lt;/phase&amp;gt;
            &amp;lt;goals&amp;gt;
                &amp;lt;goal&amp;gt;shade&amp;lt;/goal&amp;gt;
            &amp;lt;/goals&amp;gt;
            &amp;lt;configuration&amp;gt;
                &amp;lt;finalName&amp;gt;jmh-ex&amp;lt;/finalName&amp;gt;
                &amp;lt;transformers&amp;gt;
                    &amp;lt;transformer implementation=&quot;org.apache.maven.plugins.shade.resource.ManifestResourceTransformer&quot;&amp;gt;
                        &amp;lt;mainClass&amp;gt;org.openjdk.jmh.Main&amp;lt;/mainClass&amp;gt;
                    &amp;lt;/transformer&amp;gt;
                &amp;lt;/transformers&amp;gt;
            &amp;lt;/configuration&amp;gt;
        &amp;lt;/execution&amp;gt;
    &amp;lt;/executions&amp;gt;
&amp;lt;/plugin&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span&gt;예제 코드&lt;/span&gt;&lt;/h1&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;기본적으로 JMH에서 지원하는 어노테이션 기반으로 테스트 코드를 작성하면 된다. &lt;br /&gt;다음은 HashSet, TreeSet, LinkedHash의 순회 속도를 비교하는 간단한 예제이다.&lt;/p&gt;
&lt;pre id=&quot;code_1700378803661&quot; class=&quot;java&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@State(Scope.Benchmark)
@Fork(1)
@Warmup(iterations = 2)
@Measurement(iterations = 3, time = 1, timeUnit = TimeUnit.SECONDS)
@BenchmarkMode(Mode.AverageTime)
public class BenchmarkSet {

    private int size = 1_000_000;
    private Set&amp;lt;String&amp;gt; hashSet;
    private Set&amp;lt;String&amp;gt; treeSet;
    private Set&amp;lt;String&amp;gt; linkedHashSet;

    @Setup(Level.Invocation)
    public void setup() {
        hashSet = new HashSet&amp;lt;&amp;gt;();
        treeSet = new TreeSet&amp;lt;&amp;gt;();
        linkedHashSet = new LinkedHashSet&amp;lt;&amp;gt;();

        for (int i = 0; i &amp;lt; size; i++) {
            String str = UUID.randomUUID().toString();

            hashSet.add(str);
            treeSet.add(str);
            linkedHashSet.add(str);
        }
    }

    @TearDown
    public void tearDown() {
        hashSet = null;
        treeSet = null;
        linkedHashSet = null;
    }

    @Benchmark
    public String iterateHashSet() {
        Iterator&amp;lt;String&amp;gt; iterator = hashSet.iterator();
        String res = null;

        while (iterator.hasNext())
            res = iterator.next();

        return res;
    }

    @Benchmark
    public String iterateTreeSet() {
        Iterator&amp;lt;String&amp;gt; iterator = treeSet.iterator();
        String res = null;

        while (iterator.hasNext())
            res = iterator.next();

        return res;
    }

    @Benchmark
    public String iterateLinkedHashSet() {
        Iterator&amp;lt;String&amp;gt; iterator = linkedHashSet.iterator();
        String res = null;

        while (iterator.hasNext())
            res = iterator.next();

        return res;
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;jar 패키징 후 실행하면&lt;/p&gt;
&lt;pre id=&quot;code_1700378906704&quot; class=&quot;properties&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;# packaging
mvn clean package

# run
java -jar target/benchmark-ex.jar&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;각 테스트에 대한 결과를 얻을 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1700378954579&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Benchmark                           Mode  Cnt     Score       Error  Units
BenchmarkSet.iterateHashSet        thrpt    3  3286.341 &amp;plusmn;  2083.538  ops/s
BenchmarkSet.iterateLinkedHashSet  thrpt    3  6190.420 &amp;plusmn; 11425.523  ops/s
BenchmarkSet.iterateTreeSet        thrpt    3  3402.930 &amp;plusmn;  8328.370  ops/s&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span&gt;어노테이션&lt;/span&gt;&lt;/h1&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;@State&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;벤치마크 메서드에서 사용할 변수를 메서드 외부에서 초기화하고 파라미터로 전달받을 수 있는데, 이때의 파라미터를 &quot;state&quot; 변수라고 한다. 클래스에 @State 어노테이션을 기재했을 때 유효하고 어노테이션의 모드별로 파라미터의 상태를 다르게 가져갈 수 있다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; style=&quot;background-color: #f0fff0; color: #000000; text-align: left;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class MyBenchmark {

    @State(Scope.Thread)
    public static class MyState {
        public int a = 1;
        public int b = 2;
        public int sum ;
    }


    @Benchmark @BenchmarkMode(Mode.Throughput) @OutputTimeUnit(TimeUnit.MINUTES)
    public void testMethod(MyState state) {
        state.sum = state.a + state.b;
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;table style=&quot;color: #333333; text-align: start; border-collapse: collapse; width: 93.0233%; height: 61px;&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: 15.7294%; height: 20px;&quot;&gt;&lt;i&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot;&gt;Thread&lt;/span&gt;&lt;/b&gt;&lt;/i&gt;&lt;/td&gt;
&lt;td style=&quot;width: 59.2706%; height: 20px;&quot;&gt;Thread 별로 상태 객체 생성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 15.7294%; height: 20px;&quot;&gt;&lt;i&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;Group&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/i&gt;&lt;/td&gt;
&lt;td style=&quot;width: 59.2706%; height: 20px;&quot;&gt;&lt;span&gt;Thread 그룹별로 상태 객체 생성&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 15.7294%; height: 21px;&quot;&gt;&lt;i&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot;&gt;Benchmark&lt;/span&gt;&lt;/b&gt;&lt;/i&gt;&lt;/td&gt;
&lt;td style=&quot;width: 59.2706%; height: 21px;&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;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;@Setup, @TearDown&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상태(@State) 클래스의 메서드에 기재하는 어노테이션으로, @Setup 메서드는 벤치마크가 시작되기 전에 호출되고, @TearDown 메서드는 벤치마크가 종료되고 호출된다.&lt;/p&gt;
&lt;pre id=&quot;code_1700383605815&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class MyBenchmark {

    @State(Scope.Thread)
    public static class MyState {

        @Setup(Level.Trial)
        public void doSetup() {
            sum = 0;
            System.out.println(&quot;Do Setup&quot;);
        }

        @TearDown(Level.Trial)
        public void doTearDown() {
            System.out.println(&quot;Do TearDown&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;@Setup과 @TearDown이 호출되는 레벨을 지정할 수 있는데 다음과 같은 옵션이 있다.&lt;/p&gt;
&lt;table style=&quot;color: #333333; text-align: start; border-collapse: collapse; width: 93.0233%; height: 61px;&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: 15.7294%; height: 20px;&quot;&gt;&lt;i&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot;&gt;Trial&lt;/span&gt;&lt;/b&gt;&lt;/i&gt;&lt;/td&gt;
&lt;td style=&quot;width: 59.2706%; height: 20px;&quot;&gt;벤치마크를 실행할 때마다 호출되며 벤치마크의 실행은 fork를 의미&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 15.7294%; height: 20px;&quot;&gt;&lt;i&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;Iteration&lt;/span&gt;&lt;/b&gt;&lt;/i&gt;&lt;/td&gt;
&lt;td style=&quot;width: 59.2706%; height: 20px;&quot;&gt;&lt;span&gt;벤치마크 메서드의 iteration이 돌 때마다 호출&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 15.7294%; height: 21px;&quot;&gt;&lt;i&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot;&gt;Invocation&lt;/span&gt;&lt;/b&gt;&lt;/i&gt;&lt;/td&gt;
&lt;td style=&quot;width: 59.2706%; height: 21px;&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;참고로 Setup이나 TearDown 작업은 벤치마크 성능 측정에 포함되지 않는다.&lt;/p&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;&lt;b&gt;@Fork&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;벤치마크를 수행할 때 몇 번의 프로세스(= fork)를 생성할지 지정하는 데 사용한다. 여기서 fork는 독립적인 JVM에서 벤치마크를 실행하는 것을 의미한다. &lt;span style=&quot;color: #0f0f0f; text-align: start;&quot;&gt;예를 들어, &lt;/span&gt;@Fork(3)&lt;span style=&quot;color: #0f0f0f; text-align: start;&quot;&gt;이라고 선언하면 각 벤치마크는 세 번 별도의 프로세스에서 실행되고 이렇게 실행된 결과를 취합해서 최종 성능을 계산한다. 일관성을 높이기 위해 사용되며 기본 값은 5로 설정되어 있다.&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #0f0f0f; text-align: start;&quot;&gt;@Warmup&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #0f0f0f; text-align: start;&quot;&gt;벤치마크를 실행하기 전에 웜업을 몇 번 수행할지 지정하는 데 사용된다. 웜업은 벤치마크 메서드가 실제 측정되기 전에 JVM을 최적화하고, JIT 컴파일러가 코드를 더 효율적으로 변환하도록 하는 작업이다.&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #0f0f0f; text-align: start;&quot;&gt;&lt;b&gt;@Measurement&lt;/b&gt;&lt;span style=&quot;color: #0f0f0f; text-align: start;&quot;&gt; &lt;/span&gt; &lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #0f0f0f; text-align: start;&quot;&gt;벤치마크를 몇 번 반복해서 측정할지 지정하는 데 사용된다. 측정은 실제 벤치마크 측정이 수행되는 단위를 말한다. 예를 들어, 다음과 같이 기재하면 벤치마크 메서드를 3회 반복하고 측정을 1초 동안 수행한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1700381480740&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Measurement(iterations = 3, time = 1, timeUnit = TimeUnit.SECONDS)&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;@BenchmarkMode&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;벤치마크 결과를 어떤 형태로 측정할지 결정하는 어노테이션으로, 다음과 같은 옵션을 제공한다.&lt;b&gt;&lt;i&gt;&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 93.0232%; height: 180px;&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: 15.7294%; height: 20px;&quot;&gt;&lt;i&gt;&lt;b&gt;Throughput&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/i&gt;&lt;/td&gt;
&lt;td style=&quot;width: 59.2706%; height: 20px;&quot;&gt;1초당 얼마나 많은 작업을 수행하지는 측정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 15.7294%; height: 20px;&quot;&gt;&lt;i&gt;&lt;b&gt;AverageTime&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/i&gt;&lt;/td&gt;
&lt;td style=&quot;width: 59.2706%; height: 20px;&quot;&gt;&lt;span&gt;평균 실행 시간 측정&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 15.7294%; height: 21px;&quot;&gt;&lt;i&gt;&lt;b&gt;SampleTime&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/i&gt;&lt;/td&gt;
&lt;td style=&quot;width: 59.2706%; height: 21px;&quot;&gt;최대, 최소 시간등을 포함한 시간 측정&amp;nbsp;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 15.7294%;&quot;&gt;&lt;i&gt;&lt;b&gt; &lt;i&gt;&lt;b&gt;SingleShotTime&lt;/b&gt;&lt;/i&gt; &lt;/b&gt;&lt;/i&gt;&lt;/td&gt;
&lt;td style=&quot;width: 59.2706%;&quot;&gt;메서드 단일 실행 시간 측정 (JVM 워밍업 없이 cold start에 유용하게 사용)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 15.7294%;&quot;&gt;&lt;i&gt;&lt;b&gt;&lt;i&gt;&lt;b&gt; &lt;i&gt;&lt;b&gt;All&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/i&gt; &lt;/b&gt;&lt;/i&gt;&lt;/b&gt;&lt;/i&gt;&lt;/td&gt;
&lt;td style=&quot;width: 59.2706%;&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IDE에서 직접 실행한다면 다음과 같이 main 메서드를 작성하고 실행한다.&lt;/p&gt;
&lt;pre id=&quot;code_1700383911669&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@State(Scope.Benchmark)
public class BenchmarkSet {
    ```
    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
                .include(BenchmarkSet.class.getSimpleName())
                .forks(1)
                .warmupIterations(2)
                .measurementIterations(2)
                .measurementTime(TimeValue.seconds(1))
                .timeUnit(TimeUnit.SECONDS)
                .mode(Mode.AverageTime)
                .build();

        new Runner(opt).run();
    }
}&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;h1 style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span&gt;Dead Code 제거&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Dead Code&lt;/b&gt;란 프로그램의 실행 흐름에서 실제 사용되지 않는 코드를 의미한다. 성능 측정 도중에는 이러한 Dead Code가 JIT 컴파일러에 의해 최적화되어 벤치마크 결과가 왜곡될 수 있다. 예를 들어, JVM이 어떤한 계산 결과가 전혀 사용되지 않음을 알면 코드를 제거할 수 있는데 아래 코드가 이에 해당한다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;background-color: #f0fff0; color: #000000; text-align: left;&quot;&gt;&lt;code&gt;public class MyBenchmark {

    @Benchmark
    public void testMethod() {
        int a = 1;
        int b = 2;
        int sum = 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;JVM은 sum에 할당된 값이 전혀 사용되지 않음을 감지하고 데드 코드로 간주해서 &lt;b&gt;&lt;i&gt;int sum = a + b;&lt;/i&gt;&lt;/b&gt; 코드를 제거한다. 결과적으로 &lt;b&gt;&lt;i&gt;a&lt;/i&gt;&lt;/b&gt;와 &lt;b&gt;&lt;i&gt;b&lt;/i&gt;&lt;/b&gt;의 사용처가 사라지면 자연스럽게 이 변수들도 마찬가지로 데드 코드로 간주되어 메서드에는 어떠한 코드도 남아있지 않게 되면서 벤치마크의 결과를 신뢰할 수 없게 된다. &lt;br /&gt;&lt;br /&gt;여기엔 두 가지 해결 방안이 있는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;첫째는&lt;/b&gt; 계산 결과를 벤치마크&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;메서드 반환값으로 사용&lt;/b&gt;하는 것이고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;둘째는&lt;/b&gt;&amp;nbsp;JMH에서 제공하는 &lt;b&gt;Blackhole&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;i&gt;&lt;b&gt;메서드 반환값으로 사용&lt;/b&gt;&lt;/i&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 계산된 값을 반환하면 호출하는 쪽에서 값을 사용할 수 있으니 JVM이 코드를 삭제하는 불상사는 없어진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;만약 데드 코드로 제거될 수 있는 여러 값을 만드는 경우 값을 담는 컨테이너 객체를 만들어서 반환해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1700385368733&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class MyBenchmark {

    @Benchmark
    public int testMethod() {
        int a = 1;
        int b = 2;
        int sum = a + b;

        return sum;
    }
}&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;i&gt;Blackhole&lt;/i&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JMH에서 제공하는 &lt;i&gt;&lt;b&gt;Blackhole&lt;/b&gt;&lt;/i&gt;은 계산 결과를 &quot;소비(consume)&quot; 하는데 사용되는 클래스로 다음과 같이 사용한다.&lt;/p&gt;
&lt;pre id=&quot;code_1700385865441&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class MyBenchmark {

   @Benchmark
   public void testMethod(Blackhole blackhole) {
        int a = 1;
        int b = 2;
        int sum = a + b;
        blackhole.consume(sum);
    }
}&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;i&gt;Blackhole&lt;/i&gt;&lt;/b&gt;&lt;span style=&quot;color: #0f0f0f; text-align: start;&quot;&gt;을 메서드의 매개변수로 받고 &lt;/span&gt;&lt;i&gt;&lt;b&gt;blackhole.consume(result)&lt;/b&gt;&lt;/i&gt;&lt;span style=&quot;color: #0f0f0f; text-align: start;&quot;&gt;과 같이 결과를 소비하는 형태로 사용한다. 이렇게 하면 벤치마크에서 계산한 결과를 실제로 사용하지 않더라도 JIT 최적화 등을 방지해서 벤치마크 결과가 왜곡되지 않도록 한다.&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;</description>
      <category>Java/Testing</category>
      <author>Kangworld</author>
      <guid isPermaLink="true">https://kangworld.tistory.com/325</guid>
      <comments>https://kangworld.tistory.com/325#entry325comment</comments>
      <pubDate>Sun, 19 Nov 2023 23:17:08 +0900</pubDate>
    </item>
    <item>
      <title>[2023.11.12] 생존</title>
      <link>https://kangworld.tistory.com/323</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_20231112_223931491.jpg&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UKPxG/btsAaiST2S2/HKuE1QA9kJsW1KYTHEJMaK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UKPxG/btsAaiST2S2/HKuE1QA9kJsW1KYTHEJMaK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UKPxG/btsAaiST2S2/HKuE1QA9kJsW1KYTHEJMaK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUKPxG%2FbtsAaiST2S2%2FHKuE1QA9kJsW1KYTHEJMaK%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;400&quot; height=&quot;533&quot; data-filename=&quot;KakaoTalk_20231112_223931491.jpg&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;2023.04.14 금요일&lt;/i&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;몸 푸는 추신수&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;승요답게 경기는 승리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;3024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b2AOLl/btsAhcDRM3W/aR006bhdg1DtAwPA8DhYQk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b2AOLl/btsAhcDRM3W/aR006bhdg1DtAwPA8DhYQk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b2AOLl/btsAhcDRM3W/aR006bhdg1DtAwPA8DhYQk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb2AOLl%2FbtsAhcDRM3W%2FaR006bhdg1DtAwPA8DhYQk%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;500&quot; height=&quot;500&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;3024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;2023.06.04 일요일&lt;/i&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;어느 한적한 도시의 &lt;s&gt;자라&lt;/s&gt; 거북이&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;너무 빤질거려서 모형인 줄&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; 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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;3024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4EwaM/btsAhfAzNdh/oQ67U1VjqR73YxtKMljjk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4EwaM/btsAhfAzNdh/oQ67U1VjqR73YxtKMljjk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4EwaM/btsAhfAzNdh/oQ67U1VjqR73YxtKMljjk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4EwaM%2FbtsAhfAzNdh%2FoQ67U1VjqR73YxtKMljjk1%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;500&quot; height=&quot;500&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;3024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;2023.09.30 토요일&lt;/i&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;올해도 어김없이 자란 엄빠 밭의 롬복&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;매웠다&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;한동안 블로그 활동을 안 했다&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;회사일도 바빴지만&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;블로그를 대하는 내 태도가 불량해졌다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;어느 순간 글 쓰는 게 조금 귀찮고 부담스럽달까...&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;마음은 조금씩 떠나갔지만&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;공부는 &lt;span style=&quot;color: #333333; text-align: center;&quot;&gt;꾸준히&lt;span&gt; &lt;/span&gt;&lt;/span&gt;해왔고&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;조만간 다시 끄적이겠다는 다짐은 있었는데&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 길어질지 몰랐다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;어떤 개념을 잘 정리해서 퍼블릭한 공간에 올리는 것과&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그냥 머릿속에 저장하는 건&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;지식을 대하는 태도나 이해하는 측면에서 깊이가 다른 것 같다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그런 의미에서 다시 블로그의 필요성을...&lt;/p&gt;</description>
      <category>일상</category>
      <author>Kangworld</author>
      <guid isPermaLink="true">https://kangworld.tistory.com/323</guid>
      <comments>https://kangworld.tistory.com/323#entry323comment</comments>
      <pubDate>Sun, 12 Nov 2023 22:47:44 +0900</pubDate>
    </item>
    <item>
      <title>[리팩토링] 악취 25 : 주석</title>
      <link>https://kangworld.tistory.com/321</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;refactoring.jpg&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;515&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgflvb/btrX1P2a982/hxVCyU7DFkLh6Yq2eg67kK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgflvb/btrX1P2a982/hxVCyU7DFkLh6Yq2eg67kK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgflvb/btrX1P2a982/hxVCyU7DFkLh6Yq2eg67kK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbgflvb%2FbtrX1P2a982%2FhxVCyU7DFkLh6Yq2eg67kK%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;600&quot; height=&quot;386&quot; data-filename=&quot;refactoring.jpg&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;515&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;h1&gt;&lt;span&gt;✍️ 악취 25 : 주석&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;b&gt; '함수 추출하기'&lt;/b&gt;를 사용해서 별도의 메서드로 분리할 수 있고, 장황하고 긴 이름의 메서드는 &lt;b&gt;'함수 선언부 변경하기'&lt;/b&gt;를 적용할 수 있다. 주석에 명시된 시스템의 규약을 &lt;b&gt;'어서션 추가하기'&lt;/b&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 style=&quot;color: #000000;&quot;&gt;여기 주석 악취를 해결하기 위한 세 가지 리팩토링 기법이 있다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1. &quot;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;함수 추출하기&lt;/span&gt;&lt;/b&gt;&quot;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;주석이 달린 코드 뭉치가 있다면&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2. &quot;&lt;b&gt;함수 선언부 변경하기&lt;/b&gt;&quot; &lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;주석이 달린 &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;장황하고 긴 이름을 가진 메서드가 있다면&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3. &quot;&lt;span style=&quot;color: #006dd7;&quot;&gt;어서션 추가하기&lt;/span&gt;&quot; &lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&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: #006dd7;&quot;&gt;어서션 추가하기&lt;/span&gt;'&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;  어서션 추가하기&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드로 표현하지는 않았지만 기본적으로 가정하고 있는 조건들이 있다. 그런 조건은 코드를 이해하거나 '주석'을 읽으면서도 확인할 수 있지만 Assertion을 보고 직관적으로 이해하는 방법도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Assertion은 if나 switch와 달리 '항상' true를 기대하는 조건을 표현할 때 사용된다. 개발 과정에서 Assertion에서 실패한다면 프로그래머의 실수로 판단해서 논리적인 오류에 대응하거나 방어 코드를 작성할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주석으로 작성된 규약을 Assertion으로 변경할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class Customer {

    /**
     * discountRate은 0 보다 커야한다.
     */
    private double discountRate;

    public Customer(double discountRate) {
        this.discountRate = discountRate;
    }

    public double applyDiscount(double amount) {
        return amount - (this.discountRate * amount);
    }
}&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;Assertion 적용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;* Assertion은 별도의 옵션이 없다면 런타임에 조건을 체크하지 않으며 오버헤드가 있기에 상용 코드에선 포함하지 않고 개발 코드에서만 사용하는 것을 권장한다. &lt;span style=&quot;color: #8a0829;&quot;&gt;따라서 개발 과정에서&amp;nbsp;Assertion으로 시스템의 홀을 발견했다면 Assertion을 제거하고 반드시 논리적 오류를 수정하거나&amp;nbsp;방어 코드로 변경해야 한다.&lt;/span&gt;&lt;/i&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class Customer {

    private double discountRate;

    public Customer(double discountRate) {
        assert discountRate &amp;gt; 0;

        this.discountRate = discountRate;
    }

    public double applyDiscount(double amount) {
        return amount - (this.discountRate * amount);
    }

}

// 방어 코드 추가
public Customer(double discountRate) {
    if(discountRate &amp;lt;= 0)
        throw new IllegalArgumentException();

    this.discountRate = discountRate;
}&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>리팩토링</category>
      <author>Kangworld</author>
      <guid isPermaLink="true">https://kangworld.tistory.com/321</guid>
      <comments>https://kangworld.tistory.com/321#entry321comment</comments>
      <pubDate>Thu, 16 Feb 2023 15:19:54 +0900</pubDate>
    </item>
    <item>
      <title>[리팩토링] 악취 24 : 상속 포기</title>
      <link>https://kangworld.tistory.com/320</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;refactoring.jpg&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;515&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JztGJ/btrX3tDK29o/EjjMfpv2KlAdyJzwWZvkY0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JztGJ/btrX3tDK29o/EjjMfpv2KlAdyJzwWZvkY0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JztGJ/btrX3tDK29o/EjjMfpv2KlAdyJzwWZvkY0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJztGJ%2FbtrX3tDK29o%2FEjjMfpv2KlAdyJzwWZvkY0%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;600&quot; height=&quot;386&quot; data-filename=&quot;refactoring.jpg&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;515&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;h1&gt;&lt;span&gt;✍️ 악취 24 : 상속 포기&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;서브 클래스에 필드와 메서드가 빈약하다면 서브 클래스가 가져야 할 필드나 메서드를 슈퍼클래스에서 가지고 있을 확률이 크다. 서브 클래스를 제거하거나 서브 클래스로 &lt;b&gt;'필드/메서드 내려주기'&lt;/b&gt;를 적용하면 문제는 해결된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;br /&gt;만약 서브 클래스가 슈퍼 클래스의 기능은 재사용 하고 싶지만 인터페이스가 가진 규약은 따르고 싶지 않다면 &lt;b&gt;'슈퍼 클래스 또는 서브 클래스를 위임으로 변경하기'&lt;/b&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;&lt;span style=&quot;color: #000000;&quot;&gt;여기 상속 포기 악취를 해결하기 위한 두 가지 리팩토링 기법이 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1. '&lt;b&gt;필드/메서드 내려주기'&lt;/b&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2. &lt;b&gt;'슈퍼 클래스 또는 서브 클래스를 위임으로 변경하기'&lt;/b&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;  필드/메서드 내려주기&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;슈퍼 클래스에서 &lt;/span&gt;할당량(Quota)를 선언했고 서브 클래스가 이를 상속을 받는 구조이다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class Employee {

    protected Quota quota;

    protected Quota getQuota() {
        return new Quota();
    }

}

public class Engineer extends Employee {

}

public class Salesman extends Employee {

}&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;&quot;&gt;Salesman만 &lt;span style=&quot;background-color: #ffffff;&quot;&gt;할당량(Quota)을 필요로 한다면 슈퍼 클래스 과도한 책임이므로 필드와 메서드를 내려준다.&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class Employee {

}

public class Salesman extends Employee {

    protected Quota quota;

    protected Quota getQuota() {
        return new Quota();
    }
}

public class Engineer extends Employee {

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;&lt;span&gt;  &lt;span style=&quot;color: #000000;&quot;&gt;슈퍼 클래스 또는 서브 클래스를 위임으로 변경하기&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;슈퍼 클래스가 가진 기능을 재사용하고 싶지만 인터페이스가 가진 규악은 따르고 싶지 않다면 상속 구조를 위임으로 변경할 수 있다. &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;List를 상속받아 구현한 Stack&lt;/b&gt;&lt;/span&gt; &lt;b&gt;vs&lt;span style=&quot;color: #ee2323;&quot;&gt; List를 위임해서 구현한 Stack&lt;/span&gt;&lt;/b&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;&lt;br /&gt;List의 get은 인덱스 기반으로 동작하는 반면 Stack의 peek은 최상위 엘리먼트에 대해서 동작한다. Stack 입장에선 List가 제공하는 기능을 재사용하고 싶지만 peek만 보더라도 본래의 기능적 규약을 위반했다.&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class SimpleList {

    protected List&amp;lt;Integer&amp;gt; list = new ArrayList&amp;lt;&amp;gt;();

    public boolean add(int value) {
        return list.add(value);
    }

    public int get(int index) {
        return list.get(index);
    }
    
    ...
}

public class SimpleStack extends SimpleList {

    ...
    
    public int peek() {
        if (isEmpty())
            throw new RuntimeException();

        return get(size() - 1);
    }

    public int pop() {
        if (isEmpty())
            throw new RuntimeException();

        return remove(size() - 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;인터페이스가 가진 기능적 규약을 지키기 어렵다면 과감히 상속을 버리고 위임으로 변경하는 것을 고려해야 한다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class SimpleStack {

    private SimpleList list = new SimpleList();

    ...
    
    public int peek() {
        if (list.isEmpty())
            throw new RuntimeException();

        return list.get(size() - 1);
    }

    ...
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>리팩토링</category>
      <author>Kangworld</author>
      <guid isPermaLink="true">https://kangworld.tistory.com/320</guid>
      <comments>https://kangworld.tistory.com/320#entry320comment</comments>
      <pubDate>Wed, 15 Feb 2023 23:13:45 +0900</pubDate>
    </item>
    <item>
      <title>[리팩토링] 악취 23 : 데이터 클래스</title>
      <link>https://kangworld.tistory.com/319</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;refactoring.jpg&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;515&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2bXvv/btrX6onUsVb/9pdbkWf064IkWXEh9rSOVk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2bXvv/btrX6onUsVb/9pdbkWf064IkWXEh9rSOVk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2bXvv/btrX6onUsVb/9pdbkWf064IkWXEh9rSOVk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2bXvv%2FbtrX6onUsVb%2F9pdbkWf064IkWXEh9rSOVk%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;600&quot; height=&quot;386&quot; data-filename=&quot;refactoring.jpg&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;515&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;h1&gt;&lt;span&gt;✍️ 악취 23 : 데이터 클래스&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필드와 getter/setter만 가지는 클래스를 '데이터 클래스'라고 한다. 데이터 클래스의 존재는 코드의 위치가 적절하지 않다는 방증이기도 한데, 일반적으로 필드와 필드를 사용하는 메서드는 한 클래스에 모아서 최소한의 정보만 노출하며 제 기능을 제공해야 하기 때문이다. &lt;i&gt;다만 예외적으로 데이터 전달을 위한 용도라면 필드와 getter 만으로 클래스를 작성할 수 있다.&amp;nbsp;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;public 필드를 가지고 있다면 &lt;b&gt;'레코드 캡슐화하기'&lt;/b&gt;를 적용해 보고, 불필요하게 공개된 필드는&lt;b&gt; 'getter 제거하기'&lt;/b&gt;를 변경되지 않아야 할 필드에는 &lt;b&gt;'setter 제거하기'&lt;/b&gt;를 통해 외부로 공개되는 정보는 최소화하고 클래스가 제공하는 기능에만 집중하도록 유도할 수 있다. 파편화된 필드와 메서드를 한 클래스로 모으기 위해&lt;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;'함수 추출하기'&lt;/b&gt;&lt;span&gt;와&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;'함수 옮기기'&lt;/b&gt;도&lt;span&gt;&amp;nbsp;적용할 수 있다.&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;&lt;span style=&quot;color: #000000;&quot;&gt;여기 데이터 클래스 악취를 해결하기 위한 세 가지 리팩토링 기법이 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1. &quot;&lt;span style=&quot;color: #006dd7;&quot;&gt;레코드 캡슐화하기&lt;/span&gt;&quot; &lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;public 필드가 있다면&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2. &quot;getter/setter 제거하기&quot;&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 불필요한 공개와 수정을 제거&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3. &quot;함수 추출하기/옮기기&quot; &lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&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;본 포스팅에서 관심 있게 살펴볼 리팩토링은 public 필드를 제거하기 위한 &lt;b&gt;'&lt;span style=&quot;color: #006dd7;&quot;&gt;레코드 캡슐화하기&lt;/span&gt;'&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;  레코드 캡슐화하기&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리팩토링의 저자 마틴 파울러는 다음과 같은 클래스를 '레코드'라고 정의했다.&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;레코드란 public 필드로 구성된 클래스를 말한다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;외부의 누구나 필드를 직접 참조하고 동시에 수정 가능하다면 객체의 상태를 예측하기 어려워 문제가 발생할 여지가 많아진다. 이런 경우 캡슐화를 적용해서 허용된 메서드로만 값을 참조하고 수정하도록 제어한다면 객체의 상태를 보다 수월하게 관리하고 추적할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;레코드 클래스&lt;/i&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;//before
public class Organization {

    public String name;

    public String country;

}&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;&lt;span&gt;&amp;nbsp;&lt;/span&gt;getter/setter 메서드가 필드를 감추는 목적도 있지만 내부에 로직을 작성함으로써 추상화의 이점도 가져갈 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;//after
@Getter
@AllArgsConstructor
public class Organization {

    public String name;

    public String country;

    public void setCountry(String country) {
        if (country == null || country.isEmpty())
            throw new IllegalArgumentException();

        this.country = country.toLowerCase();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>리팩토링</category>
      <author>Kangworld</author>
      <guid isPermaLink="true">https://kangworld.tistory.com/319</guid>
      <comments>https://kangworld.tistory.com/319#entry319comment</comments>
      <pubDate>Tue, 14 Feb 2023 09:08:34 +0900</pubDate>
    </item>
    <item>
      <title>[리팩토링] 악취 22 : 서로 다른 인터페이스의 대안 클래스 *</title>
      <link>https://kangworld.tistory.com/318</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;refactoring.jpg&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;515&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lgrPT/btrX3tRl7G7/JSlo4V709MxoA3JQEcpMS0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lgrPT/btrX3tRl7G7/JSlo4V709MxoA3JQEcpMS0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lgrPT/btrX3tRl7G7/JSlo4V709MxoA3JQEcpMS0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlgrPT%2FbtrX3tRl7G7%2FJSlo4V709MxoA3JQEcpMS0%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;600&quot; height=&quot;386&quot; data-filename=&quot;refactoring.jpg&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;515&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;h1&gt;&lt;span&gt;✍️ 악취 22 : 서로 다른 인터페이스의 대안 클래스&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;b&gt;함수 선언 변경하기&lt;/b&gt;'와 '&lt;b&gt;함수 옮기기&lt;/b&gt;'를 사용해서 하나의 인터페이스만을 구현하도록 구조를 변경할 수 있다. 하지만 인터페이스를 수정할 수 없는 상황도 존재한다. 외부 모듈 혹은 라이브러리를 참조하는 케이스가 그 대표적인 예시이다. &lt;i&gt;본 포스팅에선 인터페이스의 수정이 불가능한 상황에서 리팩토링을 적용하는 예제를 소개하려 한다.&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;  예제 : 적용 전&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가정해 봅시다. '이메일을 전송하는 기능'과 '알람을 전송하는 기능'이 서로 다른 인터페이스로 정의되었음을.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public interface EmailService {

    void sendEmail(EmailMessage emailMessage);
}

public interface AlertService {

    void add(AlertMessage alertMessage);
}

public class EmailMessage {

    private String title;
    private String to;
    private String from;
}


public class AlertMessage {

    private String message;
    private String 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;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class OrderManager {

    private AlertService alertService;

    public void alertOrder(Order order) {
        AlertMessage alertMessage = AlertMessage.builder()
                .message(order.getOrder() + &quot;is ordered&quot;)
                .to(order.getEmail())
                .build();

        alertService.add(alertMessage);
    }
    
}

public class ShippingManager {

    private EmailService emailService;

    public void notifyShipping(Shipping shipping) {
        EmailMessage emailMessage = EmailMessage.builder()
                .title(shipping.getOrder() + &quot;is shipped&quot;)
                .to(shipping.getEmail())
                .from(&quot;no-reply@knagworld.co.kr&quot;)
                .build();

        emailService.sendEmail(emailMessage);
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;&lt;span&gt;  예제 : 적용 후&lt;/span&gt;&lt;/h1&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;i&gt;&lt;i&gt;Order&lt;/i&gt;&lt;span&gt;와 &lt;/span&gt;&lt;i&gt;Shipping&lt;/i&gt;&lt;/i&gt;을 감싸는 인터페이스를 정의한다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;public interface Notification {
}
&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;i&gt; AlertMessage&lt;/i&gt;&lt;span&gt;와&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;i&gt;EmailMessage&lt;/i&gt;를 제공한다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@AllArgsConstructor
public class AlertNotification implements Notification {

    private Order order;

    public AlertMessage toAlertMessage() {
        return AlertMessage.builder()
                .message(order.getOrder() + &quot; is ordered&quot;)
                .to(order.getEmail())
                .build();
    }
}

@AllArgsConstructor
public class EmailNotification implements Notification {

    private Shipping shipping;

    public EmailMessage toEmailMessage() {
        return EmailMessage.builder()
                .title(shipping.getOrder() + &quot; is shipped&quot;)
                .from(&quot;no-reply@kangworld.co.kr&quot;)
                .to(shipping.getEmail())
                .build();
    }
}&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;클라이언트는 추상화된 Notification과 NotificationService 만으로 코드를 전개하고, 구체적인&lt;i&gt; AlertMessage와 AlertService, &lt;i&gt;EmailMessage와 EmailService&lt;/i&gt;&lt;/i&gt;는 구현체에서 처리하도록 변경한다.&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;public interface NotificationService {

    void sendNotification(Notification notification);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class OrderManager {

    private NotificationService notificationService;

    public void alertOrder(Order order) {
        Notification notification = new AlertNotification(order);

        notificationService.sendNotification(notification);
    }
}

public class AlertNotificationService implements NotificationService {

    private AlertService alertService;

    @Override
    public void sendNotification(Notification notification) {
        AlertNotification alertNotification = (AlertNotification) notification;

        alertService.add(alertNotification.toAlertMessage());
    }

}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>리팩토링</category>
      <author>Kangworld</author>
      <guid isPermaLink="true">https://kangworld.tistory.com/318</guid>
      <comments>https://kangworld.tistory.com/318#entry318comment</comments>
      <pubDate>Mon, 13 Feb 2023 16:06:26 +0900</pubDate>
    </item>
    <item>
      <title>[리팩토링] 악취 21 : 거대한 클래스</title>
      <link>https://kangworld.tistory.com/317</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;refactoring.jpg&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;515&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/o0z6b/btrX6oOYTSD/G3vfTA3TdZhAGb4Rq2nVxk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/o0z6b/btrX6oOYTSD/G3vfTA3TdZhAGb4Rq2nVxk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/o0z6b/btrX6oOYTSD/G3vfTA3TdZhAGb4Rq2nVxk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fo0z6b%2FbtrX6oOYTSD%2FG3vfTA3TdZhAGb4Rq2nVxk%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;600&quot; height=&quot;386&quot; data-filename=&quot;refactoring.jpg&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;515&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;h1&gt;&lt;span&gt;✍️ 악취 21 : 거대한 클래스&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 클래스가 하는 일이 너무 많아지면 필드도 많아지고 필드를 사용하는 메서드도 많아진다. 시간이 지날수록 내부 코드의 &lt;i&gt;결합도는 증가&lt;/i&gt;하고 한 클래스에 너무 많은 일을 담당하니 코드를 이해하기도 수정하기도 어려워진다. 단일 책임 원칙을 고수하라는 이유도 바로 이런 문제점 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 클라이언트가 공통적으로 호출하는 메서드가 있다면 &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;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;여기 거대한 클래스를 해결하기 위한 두 가지 리팩토링 기법이 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1. &quot;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;함수 추출하기, 클래스 추출하기&lt;/span&gt;&lt;/b&gt;&quot;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;공통된 요소들이 존재한다면&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2. &quot;&lt;span style=&quot;color: #006dd7;&quot;&gt;슈퍼 클래스 추출하기&lt;/span&gt;&quot; &lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&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 style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;슈퍼 클래스 추출하기&lt;/b&gt;&lt;/span&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;h1 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;  슈퍼 클래스 추출하기&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 개 이상의 클래스에서 &lt;i&gt;비슷한 요소(필드, 메서드)&lt;/i&gt;가 존재한다면 상속을 적용하고, 슈퍼 클래스로 &lt;b&gt;'필드 올리기'&lt;/b&gt;와 &lt;b&gt;'메서드 올리기'&lt;/b&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;i&gt;Department&lt;/i&gt;와 &lt;i&gt;Employee &lt;/i&gt;모두 'name' 필드와 '월간, 연간 비용' 계산 메서드라는 비슷한 요소가 존재한다.&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class Department {

    private String name;

    private List&amp;lt;Employee&amp;gt; staff;

    public double totalMonthlyCost() {
        return staff.stream().mapToDouble(Employee::getMonthlyCost).sum();
    }

    public double totalAnnualCost() {
        return totalMonthlyCost() * 12;
    }
}

public class Employee {

    private Integer id;

    private String name;

    private double monthlyCost;

    public double annualCost() {
        return monthlyCost * 12;
    }
}&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;br /&gt;연간 비용을 구하는 로직은 두 클래스 동일하다. 다만 월간 비용을 구하는 로직이 서로 다르기에 그 구현을 서브 클래스에 위임했다. &lt;span&gt;일종의 팩토리 메서드 패턴이 적용된 케이스이다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public abstract class Party {

    public String name;

    public abstract double getMonthlyCost();

    public double annualCost() {
        return getMonthlyCost() * 12;
    }
}

public class Department extends Party {

    private List&amp;lt;Employee&amp;gt; staff;

    public Department(String name, List&amp;lt;Employee&amp;gt; staff) {
        super(name);
        this.staff = staff;
    }

    @Override
    public double getMonthlyCost() {
        return staff.stream().mapToDouble(Employee::getMonthlyCost).sum();
    }
}

@Getter
public class Employee extends Party {

    private Integer id;

    private double monthlyCost;

    public Employee(String name, Integer id, double monthlyCost) {
        super(name);
        this.id = id;
        this.monthlyCost = monthlyCost;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>리팩토링</category>
      <author>Kangworld</author>
      <guid isPermaLink="true">https://kangworld.tistory.com/317</guid>
      <comments>https://kangworld.tistory.com/317#entry317comment</comments>
      <pubDate>Sun, 12 Feb 2023 22:26:33 +0900</pubDate>
    </item>
    <item>
      <title>[리팩토링] 악취 20 : 내부자 거래</title>
      <link>https://kangworld.tistory.com/316</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;refactoring.jpg&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;515&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmSvMg/btrX4jN87QL/Dq4PcmykKy3c0JrkG76MV1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmSvMg/btrX4jN87QL/Dq4PcmykKy3c0JrkG76MV1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmSvMg/btrX4jN87QL/Dq4PcmykKy3c0JrkG76MV1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmSvMg%2FbtrX4jN87QL%2FDq4PcmykKy3c0JrkG76MV1%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;600&quot; height=&quot;386&quot; data-filename=&quot;refactoring.jpg&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;515&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;h1&gt;&lt;span&gt;✍️ 악취 20 : 내부자 거래&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;어떤 모듈이 다른 모듈의 정보를 지나치게 많이 알고 있다면 혹은 모듈이 지나치게 많은 것을 공개하고 있다면 &lt;b&gt;강한 결합도&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;함수 옮기기&lt;/b&gt;' 혹은 '&lt;b&gt;메서드 옮기기&lt;/b&gt;' 리팩토링을 적용할 수 있다. 다른 여러 모듈에서도 동일한 현상이 발생한다면 자주 요구되는 필드와 메서드 혹은 클래스를 '&lt;b&gt;별도의 모듈로 분리&lt;/b&gt;' 할 수 있다. 동시에 정보를 최대한 감추기 위해 '&lt;b&gt;위임 숨기기&lt;/b&gt;' 도 적용할 수 있다.&amp;nbsp;&lt;br /&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 style=&quot;color: #000000;&quot;&gt;여기 내부자 거래 악취를 해결하기 위한 세 가지 리팩토링 기법이 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1. &quot;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;함수/메서드 옮기기&lt;/b&gt;&lt;/span&gt;&quot;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;적절한 위치로 옮겨서 캡슐화하기&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2. &quot;모듈로 분리하기&quot;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;여러 모듈의 참조를 캡슐화된 한곳에서 제공하기 위해&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3. &quot;위임 숨기기&quot;&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&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 style=&quot;color: #000000;&quot;&gt;본 포스팅에서 관심있게 살펴볼 리팩토링은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;&quot;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;함수/메서드 옮기기&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&quot;&lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이다.&lt;/span&gt;&lt;/p&gt;
&lt;h1 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;  함수 옮기기&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;티켓의 FastPass 조건을 검사할 때 CheckIn이 티켓의 너무 많은 정보를 알아야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@AllArgsConstructor
@Getter
public class Ticket {

    private DateTime purchasedDate;
    private boolean isPrime;

}

public class CheckIn {

    public boolean isFastPass(Ticket ticket) {
        DateTime earlyBirdDate = new DateTime(2022, 1, 1, 0, 0);
        return ticket.isPrime() &amp;amp;&amp;amp; ticket.getPurchasedDate().isBefore(earlyBirdDate);
    }
}&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;클라이언트에 너무 많은 정보를 제공하고 있음은 메서드의 위치가 적절하지 않다는 방증이기도 하다. FastPass 여부를 Ticket의 책임으로 전환하자.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;@AllArgsConstructor
public class Ticket {

    public static final DateTime EARLY_BIRD_END_DATE = new DateTime(2000, 1, 1, 0, 0);

    private DateTime purchasedDate;
    private boolean isPrime;

    public boolean isFastPass() {
        return isPrime &amp;amp;&amp;amp; purchasedDate.isBefore(EARLY_BIRD_END_DATE);
    }

}
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>리팩토링</category>
      <author>Kangworld</author>
      <guid isPermaLink="true">https://kangworld.tistory.com/316</guid>
      <comments>https://kangworld.tistory.com/316#entry316comment</comments>
      <pubDate>Sat, 11 Feb 2023 22:13:32 +0900</pubDate>
    </item>
    <item>
      <title>[리팩토링] 악취 19 : 상속 (2)</title>
      <link>https://kangworld.tistory.com/315</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;refactoring.jpg&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;515&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eSl9Kk/btrX188huC0/4uPywjkdRUtxl7NheEdlY0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eSl9Kk/btrX188huC0/4uPywjkdRUtxl7NheEdlY0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eSl9Kk/btrX188huC0/4uPywjkdRUtxl7NheEdlY0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeSl9Kk%2FbtrX188huC0%2F4uPywjkdRUtxl7NheEdlY0%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;600&quot; height=&quot;386&quot; data-filename=&quot;refactoring.jpg&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;515&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;h1&gt;&lt;span&gt;✍️ 악취 19 : 상속&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;객체지향에서 상속은 코드 재사용과 기능 확장이라는 장점이 있지만 상속이 적절하지 않은 케이도 분명 있다. &lt;br /&gt;서브 클래스는 슈퍼 클래스의 모든 기능을 지원해야 한다.(&lt;i&gt;List를 상속받는 Stack이 적절한가?&lt;/i&gt;) &lt;br /&gt;서브 클래스는 슈퍼 클래스가 가진 기능적 규약을 위반해선 안 된다. (&lt;i&gt;리스코프 치환 원칙&lt;/i&gt;)&lt;br /&gt;서브 클래스는 슈퍼 클래스의 변경에 취약한 다르게 말하면 둘의 결합도가 매우 높다. &lt;br /&gt;&lt;/span&gt;&lt;span&gt;무엇보다 &lt;b&gt;다중 상속이 불가능&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;/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 style=&quot;color: #000000;&quot;&gt;여기 상속 악취를 해결하기 위한 두 가지 리팩토링 기법이 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1. &quot;&lt;b&gt;슈퍼 클래스를 위임으로 변경하기&lt;/b&gt;&quot;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2. &quot;&lt;span style=&quot;color: #006dd7;&quot;&gt;서브 클래스를 위임으로 변경하기&lt;/span&gt;&quot;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본 포스팅에서 관심있게 살펴볼 리팩토링은 &lt;b&gt;&quot;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;서브 클래스를 위임으로 변경하기&lt;/b&gt;&lt;/span&gt;&quot;&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;&lt;span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt; &lt;/span&gt;&amp;nbsp;서브 클래스를 위임으로 변경하기&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체가 특정 상황에 예외적인 로직을 수행한다면 보통은 상속을 사용해서 일반적인 로직은 슈퍼 클래스에, 예외적인 로직은 서브 클래스에 작성한다. 하지만 상속은 반드시 한 클래스로부터만 받을 수 있기에 시간이 지남에 따라 슈퍼 클래스가 다른 클래스로 대체될 수 있다. 이처럼 상속 구조에서 상속이 아닌 구조로 변경해야할 &lt;i&gt;위임&lt;/i&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;Customer 클래스와 이를 상속받아 기능을 확장한 VipCustomer가 있다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@AllArgsConstructor
@Getter
public class Customer {

    protected int id;

    /**
     * 장기 고객
     */
    protected boolean isLongTermCustomer;

    /**
     * 멤버십 보유 여부
     */
    protected boolean hasMembership;

    public boolean canUseRoomService(){
        return isLongTermCustomer &amp;amp;&amp;amp; hasMembership;
    }

    public double discountRate() {
        double discount = 0.1;
        if (hasMembership)
            discount += 0.1;

        return discount;
    }
}

public class VipCustomer extends Customer {

    private VipBenefit benefit;

    public VipCustomer(int id, boolean isLongTermCustomer, boolean hasMembership, VipBenefit benefit) {
        super(id, isLongTermCustomer, hasMembership);

        this.benefit = benefit;
    }

    @Override
    public boolean canUseRoomService() {
        return isLongTermCustomer || hasMembership;
    }

    @Override
    public double discountRate() {
        return super.discountRate() + benefit.getExtraDiscount();
    }

    public boolean canUsePrivateRoom() {
        return benefit.getAccessibleSpace().contains(&quot;PrivateRoom&quot;);
    }
}

@AllArgsConstructor
@Getter
public class VipBenefit {

    private double extraDiscount;

    private List&amp;lt;String&amp;gt; accessibleSpace;
}&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;만약 VipCustomer가 Customer 상속을 포기해야한다면 &lt;i&gt;위임(Delegate)&lt;/i&gt;으로 대체할 수 있다. 기존 VipCustomer의 로직을 실행할 &lt;i&gt;대리인 VipCustomerDelegate&lt;/i&gt;를 생성한다.&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@AllArgsConstructor
@Getter
public class VipCustomerDelegate {

    private VipBenefit benefit;

    public boolean canAccessLounge(boolean isLongTermCustomer, boolean hasMembership) {
        return isLongTermCustomer || hasMembership;
    }

    public double discountRate(double baseRate) {
        return baseRate + benefit.getExtraDiscount();
    }

    public boolean canUsePrivateRoom() {
        return benefit.getAccessibleSpace().contains(&quot;PrivateRoom&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;&lt;i&gt;대리인&lt;span&gt;&amp;nbsp;&lt;/span&gt;VipCustomerDelegate&lt;/i&gt;를 Customer에 추가해서 delegate가 존재하면 작업을 위임하고 없다면 본 로직을 수행한다.&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@AllArgsConstructor
@Getter
public class Customer {

    private int id;

    private boolean isLongTermCustomer;

    private boolean hasMembership;

    /**
     * 서브 클래스를 위임으로
     */
    private VipCustomerDelegate delegate;

    public static Customer createCustomer(int id, boolean isLongTermCustomer, boolean hasMembership) {
        return new Customer(id, isLongTermCustomer, hasMembership);
    }

    public static Customer createCustomer(int id, boolean isLongTermCustomer, boolean hasMembership, VipBenefit benefit) {
        Customer customer = new Customer(id, isLongTermCustomer, hasMembership);
        customer.delegate = new VipCustomerDelegate(benefit);

        return customer;
    }

    public Customer(int id, boolean isLongTermCustomer, boolean hasMembership) {
        this.id = id;
        this.isLongTermCustomer = isLongTermCustomer;
        this.hasMembership = hasMembership;
    }

    public boolean canAccessLounge() {
        return delegate != null ? delegate.canAccessLounge(isLongTermCustomer, hasMembership)
                : isLongTermCustomer &amp;amp;&amp;amp; hasMembership;
    }

    public double discountRate() {
        double discount = 0.1;
        if (hasMembership)
            discount += 0.1;

        return delegate != null ? delegate.discountRate(discount) : discount;
    }

    public boolean canUsePrivateRoom() {
        return delegate != null ? delegate.canUsePrivateRoom() : false;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>리팩토링</category>
      <author>Kangworld</author>
      <guid isPermaLink="true">https://kangworld.tistory.com/315</guid>
      <comments>https://kangworld.tistory.com/315#entry315comment</comments>
      <pubDate>Fri, 10 Feb 2023 20:08:34 +0900</pubDate>
    </item>
    <item>
      <title>[리팩토링] 악취 19 : 상속 (1)</title>
      <link>https://kangworld.tistory.com/314</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;refactoring.jpg&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;515&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cRefNj/btrX6hCkCuo/73wSSVD5kGuMzyxkvrphPK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cRefNj/btrX6hCkCuo/73wSSVD5kGuMzyxkvrphPK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cRefNj/btrX6hCkCuo/73wSSVD5kGuMzyxkvrphPK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcRefNj%2FbtrX6hCkCuo%2F73wSSVD5kGuMzyxkvrphPK%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;600&quot; height=&quot;386&quot; data-filename=&quot;refactoring.jpg&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;515&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;h1&gt;&lt;span&gt;✍️ 악취 19 : 상속&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;객체지향에서 상속은 코드 재사용과 기능 확장이라는 장점이 있지만 상속이 적절하지 않은 케이도 분명 있다. &lt;br /&gt;서브 클래스는 슈퍼 클래스의 모든 기능을 지원해야 한다.(&lt;i&gt;List를 상속받는 Stack이 적절한가?&lt;/i&gt;) &lt;br /&gt;서브 클래스는 슈퍼 클래스가 가진 기능적 규약을 위반해선 안 된다. (&lt;i&gt;리스코프 치환 원칙&lt;/i&gt;)&lt;br /&gt;서브 클래스는 슈퍼 클래스의 변경에 취약한 다르게 말하면 둘의 결합도가 매우 높다. &lt;br /&gt;&lt;/span&gt;&lt;span&gt;무엇보다 &lt;b&gt;다중 상속이 불가능&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;/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 style=&quot;color: #000000;&quot;&gt;여기 상속 악취를 해결하기 위한 두 가지 리팩토링 기법이 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1. &quot;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;슈퍼 클래스를 위임으로 변경하기&lt;/b&gt;&lt;/span&gt;&quot;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2. &quot;서브 클래스를 위임으로 변경하기&quot;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본 포스팅에서 관심있게 살펴볼 리팩토링은 &lt;b&gt;&quot;&lt;span style=&quot;color: #006dd7;&quot;&gt;슈퍼 클래스를 위임으로 변경하기&lt;/span&gt;&quot;&lt;/b&gt;이다.&lt;/p&gt;
&lt;h1 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;  슈퍼 클래스를 위임으로 변경하기&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서브 클래스는 슈퍼 클래스의 모든 기능을 지원해야 한다. 슈퍼 클래스의 일부 기능만 지원한다면 상속 구조가 올바른지 의심해야 한다. List를 상속받은 Stack 구현이 그 대표적인 예시이다.&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;심플한 형태의 List 클래스 &lt;i&gt;add, get, remove, isEmpty&lt;/i&gt;를 메서드를 지원한다.&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class SimpleList {

    protected List&amp;lt;Integer&amp;gt; list = new ArrayList&amp;lt;&amp;gt;();

    public boolean add(int value) {
        return list.add(value);
    }

    public int get(int index) {
        return list.get(index);
    }

    public int remove(int index) {
        return list.remove(index);
    }

    public int size() {
        return list.size();
    }

    public boolean isEmpty() {
        return list.isEmpty();
    }
}&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;i&gt;SimpleList&lt;/i&gt;를 상속한 &lt;i&gt;SimpleStack&lt;/i&gt; 클래스도 기본적인 연산 &lt;i&gt;push, peek, pop&lt;/i&gt;을 제공한다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class SimpleStack extends SimpleList {

    public int push(int value) {
        add(value);

        return value;
    }

    public int peek() {
        if (isEmpty())
            throw new RuntimeException();

        return get(size() - 1);
    }

    public int pop() {
        if (super.isEmpty())
            throw new RuntimeException();

        return remove(size() - 1);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;i&gt;그렇다면... SimpleStack이 SimpleList를 상속받는 게 올바를까?&lt;/i&gt;&lt;br /&gt;Stack은 최상위 엘리먼트를 타깃으로 연산을 하기 때문에 &lt;i&gt;SimpleList&lt;/i&gt;의 인덱스 기반 메서드가 온전한 방향으로 사용되고 있다고 보긴 어렵다. 다르게 말하면 슈퍼 클래스의 모든 기능을 지원하지 않는 케이스에 해당하고 러프하게 보면 리스코프 치환 원칙에도 위배된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 상속 구조가 올바르지 못한 구조라고 판단이 되면 상속을 '위임'으로 변경할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class SimpleStack {

    private SimpleList list = new SimpleList();

    public int push(int value) {
        list.add(value);

        return value;
    }

    public int peek() {
        if (list.isEmpty())
            throw new RuntimeException();

        return list.get(size() - 1);
    }

    public int pop() {
        if (list.isEmpty())
            throw new RuntimeException();

        return list.remove(size() - 1);
    }

    public int size() {
        return list.size();
    }

    public boolean isEmpty() {
        return list.isEmpty();
    }
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>리팩토링</category>
      <author>Kangworld</author>
      <guid isPermaLink="true">https://kangworld.tistory.com/314</guid>
      <comments>https://kangworld.tistory.com/314#entry314comment</comments>
      <pubDate>Thu, 9 Feb 2023 18:54:55 +0900</pubDate>
    </item>
    <item>
      <title>[리팩토링] 악취 18 : 중재자</title>
      <link>https://kangworld.tistory.com/313</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;refactoring.jpg&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;515&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/I33vV/btrX4mKUWqP/6UMuKVIUQUUKdKmzsmROR0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/I33vV/btrX4mKUWqP/6UMuKVIUQUUKdKmzsmROR0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/I33vV/btrX4mKUWqP/6UMuKVIUQUUKdKmzsmROR0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FI33vV%2FbtrX4mKUWqP%2F6UMuKVIUQUUKdKmzsmROR0%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;600&quot; height=&quot;386&quot; data-filename=&quot;refactoring.jpg&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;515&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;h1&gt;&lt;span&gt;✍️ 악취 18 : 중재자&lt;/span&gt;&lt;/h1&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;span style=&quot;color: #000000;&quot;&gt;여기 중재자 악취를 해결하기 위한 리팩토링 기법이 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1. &quot;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;중재자 제거하기&lt;/span&gt;&lt;/b&gt;&quot;&amp;nbsp; &lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;중재자를 거치지 않고 클라이언트가 직접 객체를 사용하기&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;  중재자 제거하기&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'중재자 제거하기'는 캡슐화를 제거하고 메시지 체인을 사용한단 의미에서 '위임 숨기기'와 반대된다. 오히려 위임을 드러내는 리팩토링이다.&lt;br /&gt;&lt;br /&gt;* 개인적으로 메시지 체인보단 캡슐화로 필요한 정보만 제공하는것이 더 객체지향스럽다고 생각한다. 실제로도 대부분의 업무에선 캡슐화를 하는 쪽이 절대 다수였다. &lt;span style=&quot;color: #b40431;&quot;&gt;&lt;i&gt;다만 예외적인 상황은 존재한다. 예를들어, 내부 객체에 필드가 추가되면 중재자 메서드도 함께 추가돼야한다. 즉, 필드가 추가될때마다 중재자 메서드도 선형적으로 증가하는 케이스라면 위임을 제거하기도 했다. &lt;br /&gt;* 결론!&lt;/i&gt;&amp;nbsp;캡슐화의 정도에 따라 &quot;중재자 제거하기&quot;와 &quot;위임 숨기기&quot;를 적절히 조절하는 것을 고려하자.&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;클라이언트가 Person의 Information에 직접 접근하는 것을 감추고자 위임 숨기기를 적용한 케이스.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@AllArgsConstructor
public class Person {

    @Getter
    private int personId;
    private Information info;

    // 중재자
    public String getName() {
        return info.getName();
    }
    
    // 중재자
    public String getAddress() {
        return info.getAddress();
    }
}

@AllArgsConstructor
@Getter
public class Information {

    private String name;
    private String address;
}

class PersonTest {
    
    @Test
    void test() {
        Person person = new Person(1, new Information(&quot;kang&quot;, &quot;seoul&quot;));
        String address = person.getAddress();

        assertEquals(&quot;seoul&quot;, address);
    }
}&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 class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@AllArgsConstructor
@Getter
public class Person {

    private int personId;
    private Information info;
}

class PersonTest {

    @Test
    void test() {
        Person person = new Person(1, new Information(&quot;kang&quot;, &quot;seoul&quot;));
        String address = person.getInfo().getAddress();

        assertEquals(&quot;seoul&quot;, address);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>리팩토링</category>
      <author>Kangworld</author>
      <guid isPermaLink="true">https://kangworld.tistory.com/313</guid>
      <comments>https://kangworld.tistory.com/313#entry313comment</comments>
      <pubDate>Wed, 8 Feb 2023 15:42:44 +0900</pubDate>
    </item>
    <item>
      <title>[리팩토링] 악취 17 : 메시지 체인</title>
      <link>https://kangworld.tistory.com/311</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;refactoring.jpg&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;515&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmiODT/btrX2WGbfkO/j9L6tVUzPRdEKiDtUeA0k1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmiODT/btrX2WGbfkO/j9L6tVUzPRdEKiDtUeA0k1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmiODT/btrX2WGbfkO/j9L6tVUzPRdEKiDtUeA0k1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmiODT%2FbtrX2WGbfkO%2Fj9L6tVUzPRdEKiDtUeA0k1%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;600&quot; height=&quot;386&quot; data-filename=&quot;refactoring.jpg&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;515&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;h1&gt;&lt;span&gt;✍️ 악취 17 : 메시지 체인&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체에 메시지를 전달해서 기능을 수행한단 의미에서 '&lt;b&gt;메시지란 메서드 호출'&lt;/b&gt;을 의미한다. 즉, 메시지 체인이란 연속된 메서드 호출을 의미한다.&lt;/p&gt;
&lt;pre id=&quot;code_1674971436327&quot; class=&quot;less&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 메시지 체인
person.getInfo().getAddress();&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;가령 클라이언트는 Person에 Info 필드가 있고, Info에 Address 필드가 존재함을 이해해야 하만 원하는 정보를 얻을 수 있기 때문이다. 심지어 체인 구조가 변경되면 모든 클라언트의 코드가 변경된다는 단점도 존재한다.&lt;/p&gt;
&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: #000000;&quot;&gt;여기 메시지 체인을 해결하기 위한 두 가지 리팩토링 기법이 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1. &quot;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;위임 숨기기&lt;/b&gt;&lt;/span&gt;&quot;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;메시지 체인을 캡슐화하기&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2. &quot;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;함수 추출하기, 함수 옮기기&lt;/span&gt;&lt;/b&gt;&quot;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&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: #006dd7;&quot;&gt;위임 숨기기&lt;/span&gt;'&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;  위임 숨기기&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;메시지 체인은 위임 숨기기를 통해서 끊어낼 수 있다. 위임 숨기기는 일종의 &lt;b&gt;캡슐화&lt;/b&gt;에 해당하는 방식으로, 클라이언트에게 최소한의 정보만을 제공하기 위해 사용된다.&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;* 캡슐화란 클래스의 필드와 메서드를 감싸서 최소한의 정보만을 제공하는 정보 은닉 개념이다. 클래스가 불필요하게 많은 정보를 외부에 제공하면 원하지 않은 방향으로 데이터가 조작될 수 있고, 작은 수정만으로도 클라이언트 코드의 많은 수정이 요구될 수 있다.&lt;br /&gt;&lt;br /&gt;필드를 private으로 선언한다든지, 필드에 접근은 getter/setter로만 제어하는 방식이 캡슐화의 일종이다. 그뿐만 아&lt;br /&gt;니라, 객체지향에서 캡슐화를 배울 때, 필드만 캡슐화의 대상인 듯 말하지만, 메서드 자체도 캡슐의 대상이 된다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트가 Person의 Address를 알기 위해선 반드시 Info의 존재를 알아야한다.&lt;/p&gt;
&lt;pre id=&quot;code_1675444615840&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// before
@AllArgsConstructor
@Getter
public class Person {

    private int personId;
    private Information info;
}


@AllArgsConstructor
@Getter
public class Information {

    private String name;
    private String address;
}

class PersonTest {

    @Test
    void test() {
        Person person = new Person(1, new Information(&quot;kang&quot;, &quot;seoul&quot;));
        String address = person.getInfo().getAddress();

        assertEquals(&quot;seoul&quot;, address);
    }
}&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;위임을 숨긴다면 클라이언트는 getAddress만 알아도 Address를 알 수 있다. 메시지 체인의 구조 변경과 같은 내부 구현이 변경되더라도 클라이언트 코드는 그대로 유지되는 장점도 있다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;// after
@AllArgsConstructor
public class Person {

    @Getter
    private int personId;
    private Information info;

    public String getAddress() {
        return info.getAddress();
    }
}

class PersonTest {

    @Test
    void test() {
        Person person = new Person(1, new Information(&quot;kang&quot;, &quot;seoul&quot;));
        String address = person.getAddress();

        assertEquals(&quot;seoul&quot;, address);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>리팩토링</category>
      <author>Kangworld</author>
      <guid isPermaLink="true">https://kangworld.tistory.com/311</guid>
      <comments>https://kangworld.tistory.com/311#entry311comment</comments>
      <pubDate>Tue, 7 Feb 2023 11:00:39 +0900</pubDate>
    </item>
    <item>
      <title>[리팩토링] 악취 16 : 임시 필드 *</title>
      <link>https://kangworld.tistory.com/310</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;refactoring.jpg&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;515&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EoeRc/btrX1OPEYxi/bxTmJCRk9O81r5ipP6MZO0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EoeRc/btrX1OPEYxi/bxTmJCRk9O81r5ipP6MZO0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EoeRc/btrX1OPEYxi/bxTmJCRk9O81r5ipP6MZO0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEoeRc%2FbtrX1OPEYxi%2FbxTmJCRk9O81r5ipP6MZO0%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;600&quot; height=&quot;386&quot; data-filename=&quot;refactoring.jpg&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;515&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;h1&gt;&lt;span&gt;✍️ 악취 16 : 임시 필드&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;클래스의 필드가 특정 상황에만 값을 갖고 그 외의 상황에는 null 또는 기본 값 혹은 임의의 값을 가질 때 이를 '임시 필드'라고 한다. 만약 클래스가 일반 필드와 임시 필드를 동시에 갖는다면 해당 클래스와 관련된 코드를 이해하기 어려워진다. 가령 임시 필드가 언제부터 제대로 된 값을 갖는지, 임시 값의 유무에 따른 동작이 어떻게 달라지는지 이해하는 건 결코 쉬운 일이 아니다.&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;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. &quot;클래스 추출하기&quot;&lt;/b&gt; 임시 필드를 다루는 전용 클래스를 생성하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. &quot;함수 옮기기&quot;&lt;/b&gt; 기존에 임시 필드를 사용하는 메서드를 전용 클래스의 메서드로 옮기기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. &quot;&lt;span style=&quot;color: #006dd7;&quot;&gt;특이 케이스 추출하기&lt;/span&gt;&quot;&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;span&gt;본 포스팅에서 관심 있게 살펴볼 리팩토링은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;&quot;&lt;span style=&quot;color: #006dd7;&quot;&gt;특이 케이스 추출하기&lt;/span&gt;&lt;/b&gt;&lt;b&gt;&quot;&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;  특이 케이스 추출하기&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 필드의 특정 값에 따라 동작이 달라지는 코드가 반복적으로 등장한다면 해당 필드를 감싸는 별도의 클래스를 정의할 수 있다. 예외적인 기능을 한 클래스에서 관리하는 장점이 있고 동시에 코드를 이해하기 한 결 쉬워진다. 여기에 적용하는 디자인 패턴을&lt;b&gt; &quot;특이 케이스 패턴&quot;&lt;/b&gt;이라고 부르며 Null Object 패턴이 그 대표적인 예시이다.&lt;/p&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;unknown&quot;이라는 특정 값에 따라서 메서드의 동작이 달라지는 코드가 반복적으로 등장한다.&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;public class CustomerService {

    public String customerName(Customer customer) {

        String customerName;
        if (customer.getName().equals(&quot;unknown&quot;)) {
            customerName = &quot;visitor&quot;;
        } else {
            customerName = customer.getName();
        }

        return customerName;
    }

    public Benefits getBenefits(Customer customer) {
        return customer.getName().equals(&quot;unknown&quot;) ? new BasicBenefits() : customer.getBenefits();
    }

    public int getPurchaseCount(Customer customer) {
        return customer.getName().equals(&quot;unknown&quot;) ? 0 : customer.getPurchaseHistory().getPurchaseCount();
    }

}&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;특정 값에 따른 행위를 클라이언트 메서드에서 처리하지말고 Customer를 상속받는 별도의 클래스를 정의해서 예외 로직을 작성한다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class UnknownCustomer extends Customer {

    public UnknownCustomer() {
        super(&quot;unknown&quot;, new BasicBenefits(), new NullPurchaseHistory());
    }

    @Override
    public String getName() {
        return &quot;visitor&quot;;
    }

    @Override
    public boolean isUnknown() {
        return super.isUnknown();
    }
    
}

public class NullPurchaseHistory extends PurchaseHistory {

    public NullPurchaseHistory() {
        super(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;클라이언트는 필드의 값이 무엇인지 관심 없고 그저 추상화된 메서드를 호출할 뿐이다. 다만 CustomerService를 호출하는 쪽에서 필드값을 보고 Customer 그대로 전달할지 UnknownCustomer로 변환해서 전달할지 판별해야 한다.&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;public class CustomerService {

    public String customerName(Customer customer) {
        return customer.getName();
    }

    public Benefits benefits(Customer customer) {
        return customer.getBenefits();
    }

    public int purchaseCount(Customer customer) {
        return customer.getPurchaseHistory().getPurchaseCount();
    }

}
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>리팩토링</category>
      <author>Kangworld</author>
      <guid isPermaLink="true">https://kangworld.tistory.com/310</guid>
      <comments>https://kangworld.tistory.com/310#entry310comment</comments>
      <pubDate>Mon, 6 Feb 2023 12:00:08 +0900</pubDate>
    </item>
    <item>
      <title>[리팩토링] 악취 15 : 추측성 일반화</title>
      <link>https://kangworld.tistory.com/312</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;refactoring.jpg&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;515&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mUO1d/btrX3qAeT2f/j0Yg7nZuNOEzjBTkpfSKF0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mUO1d/btrX3qAeT2f/j0Yg7nZuNOEzjBTkpfSKF0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mUO1d/btrX3qAeT2f/j0Yg7nZuNOEzjBTkpfSKF0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmUO1d%2FbtrX3qAeT2f%2Fj0Yg7nZuNOEzjBTkpfSKF0%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;600&quot; height=&quot;386&quot; data-filename=&quot;refactoring.jpg&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;515&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;h1&gt;&lt;span&gt;✍️ 악취 15 : 추측성 일반화&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;지금 당장 불필요함에도 나중에 필요할 것 같다는 &lt;b&gt;추측&lt;/b&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;&lt;span&gt;여기 추측성 일반화를 위한 네 가지 리팩토링 기법이 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. &quot;계층 합치기&quot;,&lt;/b&gt; 추상 클래스를 만들었지만 유효하지 않다면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. &quot;함수 인라인, 클래스 인라인&quot;,&lt;/b&gt;&amp;nbsp; 불필요한 위임이 있다면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. &quot;함수 선언 변경하기&quot;,&lt;/b&gt;&amp;nbsp;재사용을 고려해 추가했지만 사용하지 않는 매개변수가 존재한다면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. &quot;&lt;span style=&quot;color: #006dd7;&quot;&gt;죽은 코드 제거하기&lt;/span&gt;&quot;,&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;&quot;&lt;span style=&quot;color: #006dd7;&quot;&gt;죽은 코드 제거하기&lt;/span&gt;&quot;&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;  죽은 코드 제거하기&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메서드의 도입부에서 customer의 null 여부를 체크했기에, ofNullable로 한 번 더 체크하는 것은 무의미하다.&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;// before
public class OrderService {

    public List&amp;lt;Order&amp;gt; findCustomerOrders(Customer customer, List&amp;lt;Order&amp;gt; orders) {
        if (customer == null || orders == null)
            return Collections.emptyList();

        int customerId = Optional.ofNullable(customer).map(Customer::getId).orElse(0);

        return orders.stream()
                .filter(Objects::nonNull)
                .filter(o -&amp;gt; o.getCustomerId() == customerId)
                .collect(Collectors.toList());
    }
}


// after
public class OrderService {

    public List&amp;lt;Order&amp;gt; findCustomerOrders(Customer customer, List&amp;lt;Order&amp;gt; orders) {
        if (customer == null || orders == null)
            return Collections.emptyList();

        int customerId = customer.getId();

        return orders.stream()
                .filter(Objects::nonNull)
                .filter(o -&amp;gt; o.getCustomerId() == customerId)
                .collect(Collectors.toList());
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>리팩토링</category>
      <author>Kangworld</author>
      <guid isPermaLink="true">https://kangworld.tistory.com/312</guid>
      <comments>https://kangworld.tistory.com/312#entry312comment</comments>
      <pubDate>Sat, 4 Feb 2023 12:30:10 +0900</pubDate>
    </item>
    <item>
      <title>[MySQL] MySQL JSON 함수 예제</title>
      <link>https://kangworld.tistory.com/309</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;388&quot; data-origin-height=&quot;264&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xtc2a/btrW6qf96O0/ZtTjOVlgJ6d1BScsqz3Xx1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xtc2a/btrW6qf96O0/ZtTjOVlgJ6d1BScsqz3Xx1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xtc2a/btrW6qf96O0/ZtTjOVlgJ6d1BScsqz3Xx1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fxtc2a%2FbtrW6qf96O0%2FZtTjOVlgJ6d1BScsqz3Xx1%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;388&quot; height=&quot;264&quot; data-origin-width=&quot;388&quot; data-origin-height=&quot;264&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;h1&gt;&lt;span&gt;✍️ 서론&amp;nbsp;&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MySQL 5.7부터 JSON 타입을 지원하면서 JSON 타입을 다루기 위한 여러 함수들이 추가되었다. 관련 함수의 사용법을 예제와 함께 정리해 보려 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;  테이블 + 데이터 생성&lt;/span&gt;&lt;/h1&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;i&gt;예제 테이블 생성&lt;/i&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1674557168522&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CREATE TABLE user (
  id INT(11) NOT NULL,
  info JSON NULL,
  PRIMARY KEY (id))
ENGINE = InnoDB;&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;i&gt;예제 데이터 생성&lt;/i&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1674558216995&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;INSERT user
VALUES(1, '{&quot;name&quot;: &quot;kangworld&quot;, &quot;age&quot;: 20 &quot;address&quot;: &quot;seoul&quot;, &quot;hobby&quot;: [&quot;baseball&quot;, &quot;bowling&quot;]}');&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1674558250270&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mysql&amp;gt; SELECT * FROM user;
+----+----------------------------------------------------------------------------------------+
| id | info                                                                                   |
+----+----------------------------------------------------------------------------------------+
|  1 | {&quot;age&quot;: 20, &quot;name&quot;: &quot;kangworld&quot;, &quot;hobby&quot;: [&quot;baseball&quot;, &quot;bowling&quot;], &quot;address&quot;: &quot;seoul&quot;} |
+----+----------------------------------------------------------------------------------------+&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;&lt;span&gt;  JSON 함수 예제&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;i&gt;1. JSON_OBJECT ([key,&lt;span&gt;&amp;nbsp;&lt;/span&gt;val[,&lt;span&gt;&amp;nbsp;&lt;/span&gt;key,&lt;span&gt;&amp;nbsp;&lt;/span&gt;val] ...])&lt;/i&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JSON_OBJECT는 key-value 입력값을 JSON 객체로 반환한다.&lt;/p&gt;
&lt;pre id=&quot;code_1674559273003&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mysql&amp;gt; SELECT JSON_OBJECT(&quot;name&quot;, &quot;kangworld&quot;, &quot;age&quot;, 10, &quot;address&quot;, &quot;seoul&quot;, &quot;hobby&quot;, CAST('[&quot;baseball&quot;, &quot;bowling&quot;]' AS JSON));
+------------------------------------------------------------------------------------------------------------------+
| JSON_OBJECT(&quot;name&quot;, &quot;kangworld&quot;, &quot;age&quot;, 10, &quot;address&quot;, &quot;seoul&quot;, &quot;hobby&quot;, CAST('[&quot;baseball&quot;, &quot;bowling&quot;]' AS JSON)) |
+------------------------------------------------------------------------------------------------------------------+
| {&quot;age&quot;: 10, &quot;name&quot;: &quot;kangworld&quot;, &quot;hobby&quot;: [&quot;baseball&quot;, &quot;bowling&quot;], &quot;address&quot;: &quot;seoul&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;응용해서 INSERT 쿼리에 활용 가능하다.&lt;/p&gt;
&lt;pre id=&quot;code_1674559518410&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mysql&amp;gt; INSERT user 
VALUES(2, JSON_OBJECT(&quot;name&quot;, &quot;helloworld&quot;, &quot;age&quot;, 15, &quot;address&quot;, &quot;LA&quot;, &quot;hobby&quot;, CAST('[&quot;soccer&quot;, &quot;bowling&quot;]' AS JSON)));
Query OK, 1 row affected (0.00 sec)

mysql&amp;gt; SELECT * FROM user;
+----+----------------------------------------------------------------------------------------+
| id | info                                                                                   |
+----+----------------------------------------------------------------------------------------+
|  1 | {&quot;age&quot;: 20, &quot;name&quot;: &quot;kangworld&quot;, &quot;hobby&quot;: [&quot;baseball&quot;, &quot;bowling&quot;], &quot;address&quot;: &quot;seoul&quot;} |
|  2 | {&quot;age&quot;: 15, &quot;name&quot;: &quot;helloworld&quot;, &quot;hobby&quot;: [&quot;soccer&quot;, &quot;bowling&quot;], &quot;address&quot;: &quot;LA&quot;}     |
+----+----------------------------------------------------------------------------------------+&lt;/code&gt;&lt;/pre&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;&lt;i&gt;2. JSON_ARRAY([val[,&lt;span&gt;&amp;nbsp;&lt;/span&gt;val] ...])&lt;/i&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JSON_ARRAY는 입력을 JSON 배열로 반환한다.&lt;/p&gt;
&lt;pre id=&quot;code_1674562317043&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mysql&amp;gt; SELECT JSON_ARRAY(&quot;baseball&quot;, &quot;bowling&quot;, 10);
+---------------------------------------+
| JSON_ARRAY(&quot;baseball&quot;, &quot;bowling&quot;, 10) |
+---------------------------------------+
| [&quot;baseball&quot;, &quot;bowling&quot;, 10]           |
+---------------------------------------+&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;b&gt;&lt;i&gt;3. column -&amp;gt; path (= JSON_EXTRACT)&lt;/i&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연산자 &lt;b&gt;-&amp;gt;&lt;/b&gt;로 JSON value에 접근할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1674558429561&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mysql&amp;gt; SELECT id, info-&amp;gt;'$.name', info-&amp;gt;'$.age' FROM user;
+----+----------------+---------------+
| id | info-&amp;gt;'$.name' | info-&amp;gt;'$.age' |
+----+----------------+---------------+
|  1 | &quot;kangworld&quot;    | 20            |
+----+----------------+---------------+&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;연산자 &lt;b&gt;-&amp;gt;는&lt;/b&gt; &lt;b&gt;JSON_EXTRACT&lt;/b&gt;와 &lt;b&gt;동일&lt;/b&gt;하다.&lt;/p&gt;
&lt;pre id=&quot;code_1674558545801&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mysql&amp;gt; SELECT id, JSON_EXTRACT(info, '$.name'), JSON_EXTRACT(info, '$.age') FROM user;
+----+------------------------------+-----------------------------+
| id | JSON_EXTRACT(info, '$.name') | JSON_EXTRACT(info, '$.age') |
+----+------------------------------+-----------------------------+
|  1 | &quot;kangworld&quot;                  | 20                          |
+----+------------------------------+-----------------------------+&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;value가 JSON 배열이라면 인덱스를 지정해야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1674560732888&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 두 쿼리 동일
mysql&amp;gt; SELECT info-&amp;gt;'$.hobby[0]' FROM user;
mysql&amp;gt; SELECT JSON_EXTRACT(info, '$.hobby[0]') FROM user;
+----------------------------------+
| JSON_EXTRACT(info, '$.hobby[0]') |
+----------------------------------+
| &quot;baseball&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;* WHERE 절에 응용&lt;/p&gt;
&lt;pre id=&quot;code_1674574217219&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mysql&amp;gt; SELECT * FROM user WHERE JSON_EXTRACT(info, '$.name') LIKE '%kang%';
+----+----------------------------------------------------------------------------------------+
| id | info                                                                                   |
+----+----------------------------------------------------------------------------------------+
|  1 | {&quot;age&quot;: 20, &quot;name&quot;: &quot;kangworld&quot;, &quot;hobby&quot;: [&quot;baseball&quot;, &quot;bowling&quot;], &quot;address&quot;: &quot;seoul&quot;} |
+----+----------------------------------------------------------------------------------------+&lt;/code&gt;&lt;/pre&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;i&gt;&lt;b&gt;4. JSON_INSERT(json_doc,&lt;span&gt;&amp;nbsp;&lt;/span&gt;path,&lt;span&gt;&amp;nbsp;&lt;/span&gt;val[,&lt;span&gt;&amp;nbsp;&lt;/span&gt;path,&lt;span&gt;&amp;nbsp;&lt;/span&gt;val] ...)&amp;nbsp;&lt;/b&gt;&lt;/i&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JSON 데이터에 새로운 key-value를 추가하기 위해 사용된다. &lt;b&gt;key가 없다면 입력되고 key가&amp;nbsp;있다면 입력은 무시된다.&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1674562160436&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# &quot;$.gender&quot;, &quot;W&quot; 무시됨
mysql&amp;gt; SELECT JSON_INSERT(info, &quot;$.gender&quot;, &quot;M&quot;, &quot;$.gender&quot;, &quot;W&quot;) FROM user;
+-------------------------------------------------------------------------------------------------------+
| JSON_INSERT(info, &quot;$.gender&quot;, &quot;M&quot;, &quot;$.gender&quot;, &quot;W&quot;)                                                   |
+-------------------------------------------------------------------------------------------------------+
| {&quot;age&quot;: 20, &quot;name&quot;: &quot;kangworld&quot;, &quot;hobby&quot;: [&quot;baseball&quot;, &quot;bowling&quot;], &quot;gender&quot;: &quot;M&quot;, &quot;address&quot;: &quot;seoul&quot;} |
+-------------------------------------------------------------------------------------------------------+&lt;/code&gt;&lt;/pre&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;&lt;i&gt;5. JSON_REPLACE(json_doc,&lt;span&gt;&amp;nbsp;&lt;/span&gt;path,&lt;span&gt;&amp;nbsp;&lt;/span&gt;val[,&lt;span&gt;&amp;nbsp;&lt;/span&gt;path,&lt;span&gt;&amp;nbsp;&lt;/span&gt;val] ...)&lt;/i&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JSON 데이터의 value를 수정하기 위해 사용된다. &lt;b&gt;key가 있으면 수정되고 key가&amp;nbsp;없다면 수정은 무시된다.&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1674564211837&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mysql&amp;gt; SELECT JSON_REPLACE(info, &quot;$.address&quot;, &quot;gimhae&quot;, &quot;$.abc&quot;, &quot;abc&quot;) FROM user;
+-----------------------------------------------------------------------------------------+
| JSON_REPLACE(info, &quot;$.address&quot;, &quot;gimhae&quot;, &quot;$.abc&quot;, &quot;abc&quot;)                               |
+-----------------------------------------------------------------------------------------+
| {&quot;age&quot;: 20, &quot;name&quot;: &quot;kangworld&quot;, &quot;hobby&quot;: [&quot;baseball&quot;, &quot;bowling&quot;], &quot;address&quot;: &quot;gimhae&quot;} |
+-----------------------------------------------------------------------------------------+&lt;/code&gt;&lt;/pre&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;&lt;i&gt;6. JSON_SET(json_doc,&lt;span&gt;&amp;nbsp;&lt;/span&gt;path,&lt;span&gt;&amp;nbsp;&lt;/span&gt;val[,&lt;span&gt;&amp;nbsp;&lt;/span&gt;path,&lt;span&gt;&amp;nbsp;&lt;/span&gt;val] ...)&lt;/i&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JSON 데이터의 입력 혹은 수정을 위해 사용된다. &lt;b&gt;key가 없다면 key-value가 입력되고 key가 있다면 value가 수정된다.&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1674564583159&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#age 수정, gender 추가
mysql&amp;gt; SELECT JSON_SET(info, &quot;$.age&quot;, 99, &quot;$.gender&quot;, &quot;M&quot;) FROM user;
+-------------------------------------------------------------------------------------------------------+
| JSON_SET(info, &quot;$.age&quot;, 99, &quot;$.gender&quot;, &quot;M&quot;)                                                          |
+-------------------------------------------------------------------------------------------------------+
| {&quot;age&quot;: 99, &quot;name&quot;: &quot;kangworld&quot;, &quot;hobby&quot;: [&quot;baseball&quot;, &quot;bowling&quot;], &quot;gender&quot;: &quot;M&quot;, &quot;address&quot;: &quot;seoul&quot;} |
+-------------------------------------------------------------------------------------------------------+&lt;/code&gt;&lt;/pre&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;&lt;i&gt;7. JSON_QUOTE(string)&lt;/i&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;string을 큰따옴표로 감싸서 JSON value 형태로 변환한다. (동시에 내부의 큰따옴표와 특정 문자를 이스케이프 문자로 변환한다.)&lt;/p&gt;
&lt;pre id=&quot;code_1674568516609&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mysql&amp;gt; SELECT JSON_QUOTE('hellow world');
+----------------------------+
| JSON_QUOTE('hellow world') |
+----------------------------+
| &quot;hellow world&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;*&lt;span style=&quot;background-color: #ffffff; color: #555555;&quot;&gt;&lt;span&gt; JSON_CONTAINS는 문자열만 받는 반면 다양한 타입을 변환하고 싶다면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;CAST(value&lt;span&gt;&amp;nbsp;&lt;/span&gt;AS JSON)을 사용하면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1674568796756&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# integer
mysql&amp;gt; SELECT CAST(10 as JSON);
+------------------+
| CAST(10 as JSON) |
+------------------+
| 10               |
+------------------+

# array
mysql&amp;gt; SELECT CAST('[10,20]' as JSON);
+-------------------------+
| CAST('[10,20]' as JSON) |
+-------------------------+
| [10, 20]                |
+-------------------------+

# string 
mysql&amp;gt; SELECT CAST('&quot;hello world&quot;' as JSON);
+-------------------------------+
| CAST('&quot;hello world&quot;' as JSON) |
+-------------------------------+
| &quot;hello world&quot;                 |
+-------------------------------+&lt;/code&gt;&lt;/pre&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;&lt;i&gt;8. JSON_CONTAINS(target,&lt;span&gt;&amp;nbsp;&lt;/span&gt;candidate[,&lt;span&gt;&amp;nbsp;&lt;/span&gt;path])&lt;/i&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;JSON 데이터&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;candidate가 &lt;span&gt;target 혹은 target path에 존재하는지 판단하기 위해 사용된다.&lt;br /&gt;&lt;br /&gt;(1) &lt;i&gt;JSON_CONTAINS&lt;b&gt;(target, candidate)&lt;/b&gt; case&lt;/i&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1674566563564&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# path 없는 케이스
mysql&amp;gt; SELECT JSON_CONTAINS(info, JSON_OBJECT(&quot;age&quot;, 20)) FROM user;
+--------------------------------------------+
| JSON_CONTAINS(info, JSON_OBJECT(&quot;age&quot;,20)) |
+--------------------------------------------+
|                                          1 |
+--------------------------------------------+&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;(2) &lt;i&gt;JSON_CONTAINS&lt;b&gt;(target, candidate, [, path])&lt;/b&gt; case&lt;/i&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1674567793807&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# path가 포함된 케이스
mysql&amp;gt; SELECT JSON_CONTAINS(info, JSON_QUOTE('kangworld'), '$.name') FROM user;
+--------------------------------------------------------+
| JSON_CONTAINS(info, JSON_QUOTE('kangworld'), '$.name') |
+--------------------------------------------------------+
|                                                      1 |
+--------------------------------------------------------+&lt;/code&gt;&lt;/pre&gt;</description>
      <category>DB/MySQL</category>
      <author>Kangworld</author>
      <guid isPermaLink="true">https://kangworld.tistory.com/309</guid>
      <comments>https://kangworld.tistory.com/309#entry309comment</comments>
      <pubDate>Tue, 24 Jan 2023 23:33:17 +0900</pubDate>
    </item>
    <item>
      <title>[리팩토링] 악취 14 : 성의없는 요소</title>
      <link>https://kangworld.tistory.com/307</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;refactoring.jpg&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;515&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcPhwE/btrWsU3aCeV/EXPFdLUChEAAUGGaigpnW0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcPhwE/btrWsU3aCeV/EXPFdLUChEAAUGGaigpnW0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcPhwE/btrWsU3aCeV/EXPFdLUChEAAUGGaigpnW0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbcPhwE%2FbtrWsU3aCeV%2FEXPFdLUChEAAUGGaigpnW0%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;600&quot; height=&quot;386&quot; data-filename=&quot;refactoring.jpg&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;515&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;h1&gt;&lt;span&gt;✍️ 악취 14 : 성의없는 요소&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;개발과정에서 정의된 변수, 메서드, 클래스가 시간이 지남에 따라 필요 없는 경우가 있다. 가령 확장성을 고려해서 미리 추가된 변수와 기능들이 그러한 경우에 속한다. 혹은 리팩토링의 결과로 더 이상 필요 없는 요소들이 나올 수 있다. 리팩토링 책의 저자는 이와 같은 요소를 '&lt;b&gt;성의 없는 요소&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;함수 인라인&lt;/b&gt;'을 적용할 수 있다. 이와 유사하게 클래스의 경우라면 '&lt;b&gt;클래스 인라인&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 style=&quot;color: #000000;&quot;&gt;여기 성의 없는 요소 악취를 해결하기 위한 세 가지 리팩토링 기법이 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1. &quot;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;함수 인라인&lt;/span&gt;&lt;/b&gt;&quot;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2. &quot;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;클래스 인라인&lt;/span&gt;&lt;/b&gt;&quot;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3. &quot;계층 합치기&quot; &lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&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;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;  계층 합치기&lt;/span&gt;&lt;/h1&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 class=&quot;java&quot;&gt;&lt;code&gt;@AllArgsConstructor
@Getter
public abstract class Customer {

    protected double baseDiscountRate;

    public double getDiscountRate() {
        return baseDiscountRate;
    }
    
    public abstract int applyDiscount(int price, int deliveryFee);
    
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;public class GoldCustomer extends Customer {

    private int monthlyPoint;

    public GoldCustomer(double baseDiscountRate, int monthlyPoint) {
        super(baseDiscountRate);
        this.monthlyPoint = monthlyPoint;
    }

    @Override
    public int applyDiscount(int price, int deliveryFee) {
        int discountedPrice = price;
        if (monthlyPoint &amp;gt; 0) {
            int pointDiscount = Math.min(discountedPrice, monthlyPoint);

            discountedPrice -= pointDiscount;
            monthlyPoint -= pointDiscount;
        }

        discountedPrice *= (1.0 - getDiscountRate());

        return discountedPrice + deliveryFee;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;public class VipCustomer extends Customer {

    private int freeDeliveryCnt;

    public VipCustomer(double baseDiscountRate, int freeDeliveryCnt) {
        super(baseDiscountRate);
        this.freeDeliveryCnt = freeDeliveryCnt;
    }

    @Override
    public double getDiscountRate() {
        return baseDiscountRate + 0.2;
    }

    @Override
    public int applyDiscount(int price, int deliveryFee) {
        int discountedPrice = price;
        discountedPrice *= (1.0 - getDiscountRate());

        if (freeDeliveryCnt &amp;gt; 0) {
            freeDeliveryCnt--;

            return discountedPrice;
        }

        return discountedPrice + deliveryFee;
    }
}
&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; VipCustomer 클래스가 사라져서&lt;/b&gt; 상속이 무의미해질 때, Customer 클래스와 Gold&lt;span&gt;Customer를 하나로 묶을 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@AllArgsConstructor
@Getter
public class Customer {

    private double baseDiscountRate;
    private int monthlyPoint;

    public double getDiscountRate() {
        return baseDiscountRate;
    }

    public int applyDiscount(int price, int deliveryFee) {
        int discountedPrice = price;
        if (monthlyPoint &amp;gt; 0) {
            int pointDiscount = Math.min(discountedPrice, monthlyPoint);

            discountedPrice -= pointDiscount;
            monthlyPoint -= pointDiscount;
        }

        discountedPrice *= (1.0 - getDiscountRate());

        return discountedPrice + deliveryFee;
    }

}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>리팩토링</category>
      <author>Kangworld</author>
      <guid isPermaLink="true">https://kangworld.tistory.com/307</guid>
      <comments>https://kangworld.tistory.com/307#entry307comment</comments>
      <pubDate>Mon, 16 Jan 2023 23:45:32 +0900</pubDate>
    </item>
    <item>
      <title>[리팩토링] 악취 13 : 반복문</title>
      <link>https://kangworld.tistory.com/306</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;refactoring.jpg&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;515&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cbOcfF/btrVUzMHRgC/SSbbhKhgoaHzO7RIUjp6e1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cbOcfF/btrVUzMHRgC/SSbbhKhgoaHzO7RIUjp6e1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cbOcfF/btrVUzMHRgC/SSbbhKhgoaHzO7RIUjp6e1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcbOcfF%2FbtrVUzMHRgC%2FSSbbhKhgoaHzO7RIUjp6e1%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;600&quot; height=&quot;386&quot; data-filename=&quot;refactoring.jpg&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;515&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;h1&gt;&lt;span&gt;✍️ 악취 13 : 반복문&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그래밍 언어 초기엔 for와 while을 사용해서 반복문을 구현했지만, 비교적 최근 Java와 같은 언어에서 함수형 프로그래밍을 지원하게 되며 반복문의 대안인 &lt;b&gt;파이프라인&lt;/b&gt;이 등장했다. Java의 Stream이 대표적인 예시인데, Collection의 Stream을 열고 중개 오퍼레이션을 호출하며 연산을 수행하는 &lt;span&gt;일련의&lt;span&gt; &lt;/span&gt;&lt;/span&gt;흐름을 Stream pipeline이라 부른다. 파이프라인을 사용하면 코드가 간결해지고 의도가 명확하게 드러나 가독성이 증가한다는 장점이 있기에 충분히 활용할만하다.&lt;/p&gt;
&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;/p&gt;
&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: #000000;&quot;&gt;여기 반복문 악취를 해결하기 위한 리팩토링 기법이 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1. &quot;반복문을 파이프라인으로&quot; 파이프라인 오퍼레이션으로 의도를 명확하게&lt;br /&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;  반복문을 파이프라인으로 바꾸기&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;Java 기준, 고전적인 반복문을 Stream과 중개 오퍼레이션을 사용해서 구현한다면 코드의 가독성이 증가한다.&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 style=&quot;background-color: #ffffff;&quot;&gt;고전적인 반복문으로 구현된 코드를 Stream pipeline 형태로 변경해 보자.&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@AllArgsConstructor
@Getter
public class Employee {

    private int id;

    private String type;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;lasso&quot;&gt;&lt;code&gt;public class EmployeeService {

    public List&amp;lt;Integer&amp;gt; findManagerIds(List&amp;lt;Employee&amp;gt; employees){
        List&amp;lt;Integer&amp;gt; managerIds = new ArrayList&amp;lt;&amp;gt;();

        for(Employee employee : employees){
            if(employee.getType().equals(&quot;manager&quot;)){
                managerIds.add(employee.getId());
            }
        }

        return managerIds;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;lasso&quot;&gt;&lt;code&gt;public class EmployeeService {

    public List&amp;lt;Integer&amp;gt; findManagerIds(List&amp;lt;Employee&amp;gt; employees){
       return employees.stream()
               .filter(e-&amp;gt;e.getType().equals(&quot;manager&quot;))
               .map(Employee::getId)
               .collect(Collectors.toList());
    }
}
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>리팩토링</category>
      <author>Kangworld</author>
      <guid isPermaLink="true">https://kangworld.tistory.com/306</guid>
      <comments>https://kangworld.tistory.com/306#entry306comment</comments>
      <pubDate>Tue, 10 Jan 2023 23:55:07 +0900</pubDate>
    </item>
    <item>
      <title>[리팩토링] 악취 11 : 기본형 집착 (2)</title>
      <link>https://kangworld.tistory.com/305</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;refactoring.jpg&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;515&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lteby/btrVclBYXwc/7hGeYKIiBYmeJlaCTZRJFk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lteby/btrVclBYXwc/7hGeYKIiBYmeJlaCTZRJFk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lteby/btrVclBYXwc/7hGeYKIiBYmeJlaCTZRJFk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Flteby%2FbtrVclBYXwc%2F7hGeYKIiBYmeJlaCTZRJFk%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;600&quot; height=&quot;386&quot; data-filename=&quot;refactoring.jpg&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;515&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;h1&gt;&lt;span&gt;✍️ 악취&amp;nbsp;11&amp;nbsp;:&amp;nbsp;기본형&amp;nbsp;집착&amp;nbsp;(2)&lt;/span&gt;&lt;/h1&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;여기 기본형 집착 악취를 해결하기 위한 두 가지 리팩토링 기법이 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1. &quot;기본형을 객체로 바꾸기&quot;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2. &quot;조건부 로직을 다형성으로 바꾸기&quot;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;  조건부 로직을 다형성으로 바꾸기&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비슷하지만 서로 다른 것을 표현하는 경우 문자열, 열거형, 숫자 등을 사용하기도 한다. 가령 고객의 등급을 표현할 때 열거형을 사용하는 것이 한 예시이다.&lt;/p&gt;
&lt;pre class=&quot;crystal&quot;&gt;&lt;code&gt;public enum Grade {
    SILVER, GOLD, VIP
}
&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문에 사용되어 서로 다른 로직을 처리하는 분기문의 플래그로 사용되기도 한다. 만약 플래그 변수가 분기문에서 자주 사용되고 동시에 분기마다 서로 다른 로직을 수행하고 경우에 따라 특정 타입에만 유효한 필드가 있다면 본 리팩토링을 고려해 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 클래스와 같이 type으로 서로 다른 것을 표현하고, type을 보고 서로 다른 로직을 수행하기보단, type에 따른 별도의 서브 클래스 혹은 구현체를 정의해서 코드를 분리하는 것이 더 효율적이다.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;@AllArgsConstructor
public class Employee {

    private String type;

    private List&amp;lt;String&amp;gt; availableProjects;

    public int vacationHours() {
        switch (type) {
            case &quot;full-time&quot;:
                return 120;
            case &quot;part-time&quot;:
                return 80;
            case &quot;temporal&quot;:
                return 32;
            default:
                throw new IllegalArgumentException();
        }
    }

    public boolean canAccessTo(String project) {
        switch (type) {
            case &quot;full-time&quot;:
                return true;
                
            case &quot;part-time&quot;:
            case &quot;temporal&quot;:
                return availableProjects.contains(project);
            default:
                throw new IllegalArgumentException();
        }
    }
}&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;Employee를 추상 클래스로 변경하고 속성에 따라 별도의 서브 클래스를 정의해서 각각의 표현과 로직을 분리하는 것이 좋다.&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@AllArgsConstructor
@NoArgsConstructor
public abstract class Employee {

    protected List&amp;lt;String&amp;gt; availableProjects;

    public abstract int vacationHours();

    public boolean canAccessTo(String project){
        return availableProjects.contains(project);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;public class FullTimeEmployee extends Employee {

    @Override
    public int vacationHours() {
        return 120;
    }

    @Override
    public boolean canAccessTo(String project) {
        return true;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;public class PartTimeEmployee extends Employee {

    public PartTimeEmployee(List&amp;lt;String&amp;gt; availableProjects) {
        super(availableProjects);
    }

    @Override
    public int vacationHours() {
        return 80;
    }

}
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>리팩토링</category>
      <author>Kangworld</author>
      <guid isPermaLink="true">https://kangworld.tistory.com/305</guid>
      <comments>https://kangworld.tistory.com/305#entry305comment</comments>
      <pubDate>Mon, 2 Jan 2023 23:27:16 +0900</pubDate>
    </item>
    <item>
      <title>[리팩토링] 악취 11 : 기본형 집착 (1)</title>
      <link>https://kangworld.tistory.com/304</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;refactoring.jpg&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;515&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Cyqr7/btrU6XuQvaq/V1yPurMaEBt2dNOBTAbvg0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Cyqr7/btrU6XuQvaq/V1yPurMaEBt2dNOBTAbvg0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Cyqr7/btrU6XuQvaq/V1yPurMaEBt2dNOBTAbvg0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCyqr7%2FbtrU6XuQvaq%2FV1yPurMaEBt2dNOBTAbvg0%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;600&quot; height=&quot;386&quot; data-filename=&quot;refactoring.jpg&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;515&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;h1&gt;&lt;span&gt;✍️ 악취 11 : 기본형 집착&lt;/span&gt;&lt;/h1&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;여기 기본형 집착 악취를 해결하기 위한 두 가지 리팩토링 기법이 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1. &quot;기본형을 객체로 바꾸기&quot;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2. &quot;조건부 로직을 다형성으로 바꾸기&quot;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;  기본형을 객체로 바꾸기&lt;/span&gt;&lt;/h1&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;&lt;i&gt;STEP 1 : 문제 파악&lt;/i&gt;&lt;/h4&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@AllArgsConstructor
@Getter
public class Order {

    private String priority;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;public class OrderProcessor {

    public long numberOfHighPriorityOrders(List&amp;lt;Order&amp;gt; orders) {
        return orders.stream()
                .filter(o -&amp;gt; o.getPriority() == &quot;high&quot;)
                .count();
    }
}&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. priority는 type safety가 보장되지 않는다. 막말로 어떤 String 값이 와도 우선순위로 간주된다.&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;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;i&gt;STEP 2 : Order의 책임으로&lt;/i&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;priority의 type safety 보장과 우선순위 계산을 Order의 책임으로 구현해 보자.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;@Getter
public class Order {

    private static final List&amp;lt;String&amp;gt; LEGAL_PRIORITY = Arrays.asList(&quot;low&quot;, &quot;normal&quot;, &quot;high&quot;);

    private String priority;

    public Order(String priority) {
        validate(priority);

        this.priority = priority;
    }

    public boolean higherThan(String priority) {
        validate(priority);

        return index(this.priority) &amp;gt; index(priority);
    }

    private void validate(String priority) {
        if (!LEGAL_PRIORITY.contains(priority))
            throw new IllegalArgumentException(priority);
    }

    private int index(String priority) {
        return LEGAL_PRIORITY.indexOf(priority);
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;public class OrderProcessor {

    public long numberOfHighPriorityOrders(List&amp;lt;Order&amp;gt; orders) {
        return orders.stream()
                .filter(o -&amp;gt; o.higherThan(&quot;normal&quot;))
                .count();
    }
}
&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;위 구조는 여전히 문제점이 있다. 가장 큰 문제로, Order가 너무 많은 정보를 알고 있고 동시에 너무 많은 일을 처리한다. 가령 Order는 우선순위 검증을 위해 LEAGAL_PRIORITY도 알아야 하고, 우선순위 계산을 위한 메서드를 가지고 있다. 이는 너무 많은 기능이 Order에게 집중되었다고 볼 수 있다.&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;i&gt;STEP 3 : Priority의 책임으로&lt;/i&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본형이었던 Priority를 클래스로 변경하고 Order에게 부여했던 책임을 Priority로 옮겨보자.&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;@Getter
public class Priority {

    private static final List&amp;lt;String&amp;gt; LEGAL_PRIORITY = Arrays.asList(&quot;low&quot;, &quot;normal&quot;, &quot;high&quot;);

    private String value;

    public Priority(String value) {
        if (!LEGAL_PRIORITY.contains(value))
            throw new IllegalArgumentException(value);

        this.value = value;
    }

    public boolean higherThan(Priority priority) {
        return index(this) &amp;gt; index(priority);
    }

    private int index(Priority priority) {
        return LEGAL_PRIORITY.indexOf(priority.value);
    }

}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Getter
public class Order {

    private Priority priority;

    public Order(Priority priority) {
        this.priority = priority;
    }

}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;public class OrderProcessor {

    public long numberOfHighPriorityOrders(List&amp;lt;Order&amp;gt; orders) {
        Priority normal = new Priority(&quot;normal&quot;);

        return orders.stream()
                .filter(o -&amp;gt; o.getPriority().higherThan(normal))
                .count();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>리팩토링</category>
      <author>Kangworld</author>
      <guid isPermaLink="true">https://kangworld.tistory.com/304</guid>
      <comments>https://kangworld.tistory.com/304#entry304comment</comments>
      <pubDate>Mon, 2 Jan 2023 17:33:20 +0900</pubDate>
    </item>
    <item>
      <title>[리팩토링] 악취 10 : 데이터 뭉치</title>
      <link>https://kangworld.tistory.com/303</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;refactoring.jpg&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;515&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQQM4x/btrU44AT0XH/zoK7YUKvRRSCMdpmfDMqn1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQQM4x/btrU44AT0XH/zoK7YUKvRRSCMdpmfDMqn1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQQM4x/btrU44AT0XH/zoK7YUKvRRSCMdpmfDMqn1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQQM4x%2FbtrU44AT0XH%2FzoK7YUKvRRSCMdpmfDMqn1%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;600&quot; height=&quot;386&quot; data-filename=&quot;refactoring.jpg&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;515&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;h1&gt;&lt;span&gt;✍️ 악취 10 : 데이터 뭉치&lt;/span&gt;&lt;/h1&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;b&gt;1. &quot;클래스 추출하기&quot; 동일한, 유사한 필드를 하나의 클래스로&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. &quot;매개변수 객체 만들기&quot; 동일한, 유사한 매개변수를 하나의 클래스로&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;  클래스 추출하기&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 두 클래스 모두 학년과 반 번호라는 공통된 필드를 사용 중이라면, 두 필드를 하나의 클래스로 추출할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@AllArgsConstructor
@Getter
public class Student {

    private String name;

    private int grade;

    private int classNo;

    public String getGradeClassNo() {
        return grade + &quot;-&quot; + classNo;
    }
}

@AllArgsConstructor
@Getter
public class Teacher {

    private String name;

    /**
     * 담당 학년, 반 번호
     */
    private int grade;
    private int classNo;

    public String getGradeClassInCharge() {
        return grade + &quot;-&quot; + classNo;
    }
}&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;공통된 필드를 GradeClassNo 클래스로 추출했다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@AllArgsConstructor
public class GradeClassNo {

    private int grade;

    private int classNo;

    @Override
    public String toString() {
        return grade + &quot;-&quot; + classNo;
    }
}

@AllArgsConstructor
public class Student {

    private String name;

    private GradeClassNo gradeClassNo;

    public String getGradeClassNo() {
        return gradeClassNo.toString();
    }
}

@AllArgsConstructor
public class Teacher {

    private String name;

    private GradeClassNo gradeClassNo;

    public String getGradeClassInCharge() {
        return gradeClassNo.toString();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>리팩토링</category>
      <author>Kangworld</author>
      <guid isPermaLink="true">https://kangworld.tistory.com/303</guid>
      <comments>https://kangworld.tistory.com/303#entry303comment</comments>
      <pubDate>Mon, 2 Jan 2023 15:47:55 +0900</pubDate>
    </item>
    <item>
      <title>[리팩토링] 악취 9 : 기능 편애</title>
      <link>https://kangworld.tistory.com/302</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;refactoring.jpg&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;515&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bL8lwy/btrU432ZpW0/0uAGEq1UiK3Vpmn4SEjWX0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bL8lwy/btrU432ZpW0/0uAGEq1UiK3Vpmn4SEjWX0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bL8lwy/btrU432ZpW0/0uAGEq1UiK3Vpmn4SEjWX0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbL8lwy%2FbtrU432ZpW0%2F0uAGEq1UiK3Vpmn4SEjWX0%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;600&quot; height=&quot;386&quot; data-filename=&quot;refactoring.jpg&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;515&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;h1&gt;&lt;span&gt;✍️ 악취&amp;nbsp;9&amp;nbsp;:&amp;nbsp;기능&amp;nbsp;편애&lt;/span&gt;&lt;/h1&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;b&gt;1. &quot;함수 옮기기&quot; 올바른 책임을 위해&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;  함수 옮기기&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전기 사용량과 가스 사용량을 표현하는 두 클래스가 있고&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@AllArgsConstructor
@Getter
public class ElectricityUsage {

    private double amount;

    private double pricePerUnit;
}

@AllArgsConstructor
@Getter
public class GasUsage {

    private double amount;

    private double pricePerUnit;
}&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;총 사용료를 계산하는 Bill 클래스가 있다. 현재 구조에서 아래 Bill 클래스가 기능 편애 악취를 가지고 있다. 정확히 calculateBill 메서드가 사용료 계산이라는 기능 편애 욕심을 내고 있다. 여기엔 문제가 있는데 가령 가스 사용량에 따른 계산 방법이 변경되었다면 아래와 같은 가스 사용료 계산 코드를 모두 수정해야 하는 불상사가 발생한다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;public class Bill {

    private ElectricityUsage electricityUsage;

    private GasUsage gasUsage;

    public double calculateBill() {
        double electricityBill = electricityUsage.getAmount() * electricityUsage.getPricePerUnit();
        double gasBill = gasUsage.getAmount() * gasUsage.getPricePerUnit();

        return electricityBill + gasBill;
    }

}&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;따라서 Bill에서 사용량과 가격을 참조해서 공과금을 계산하는 것보단, 각각의 클래스에서 사용료를 계산하는 것이 더 올바른 책임이라고 생각한다. 이러한 구조는 수정에도 유연한데, 만약 가스 사용료 계산 방법이 변경되더라도 getGasBill의 코드만 수정하면 된다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;// after!

@AllArgsConstructor
@Getter
public class ElectricityUsage {

    private double amount;

    private double pricePerUnit;

    public double getElectricityBill() {
        return amount * pricePerUnit;
    }

}


@AllArgsConstructor
@Getter
public class GasUsage {

    private double amount;

    private double pricePerUnit;

    public double getGasBill() {
        return amount * pricePerUnit;
    }
}

public class Bill {

    private ElectricityUsage electricityUsage;

    private GasUsage gasUsage;

    public double calculateBill() {
        return electricityUsage.getElectricityBill() + gasUsage.getGasBill();
    }
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>리팩토링</category>
      <author>Kangworld</author>
      <guid isPermaLink="true">https://kangworld.tistory.com/302</guid>
      <comments>https://kangworld.tistory.com/302#entry302comment</comments>
      <pubDate>Mon, 2 Jan 2023 15:14:20 +0900</pubDate>
    </item>
    <item>
      <title>[리팩토링] 악취 8 : 산탄총 수술</title>
      <link>https://kangworld.tistory.com/301</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;refactoring.jpg&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;515&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bUJb3U/btrUfPD2bVE/rq2qqr2aUfxkK8Ak2aym9k/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bUJb3U/btrUfPD2bVE/rq2qqr2aUfxkK8Ak2aym9k/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bUJb3U/btrUfPD2bVE/rq2qqr2aUfxkK8Ak2aym9k/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbUJb3U%2FbtrUfPD2bVE%2Frq2qqr2aUfxkK8Ak2aym9k%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;600&quot; height=&quot;386&quot; data-filename=&quot;refactoring.jpg&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;515&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;h1&gt;&lt;span&gt;✍️ 악취 8 : 산탄총 수술&lt;/span&gt;&lt;/h1&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;b&gt;1. &quot;함수 옮기기, 필드 옮기기&quot; 필요한 변경 내역을 하나의 클래스로 모으기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. &quot;단계 쪼개기&quot; 공통으로 사용되는 메서드의 결과물을 하나로 묶기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. &quot;함수 인라인, 클래스 인라인&quot; 흩어진 로직을 한 곳으로&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;  필드 옮기기&lt;/span&gt;&lt;/h1&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;b&gt;1. &quot;어떤 데이터를 항상 어떤 객체와 함께 전달하는 경우&quot;&lt;/b&gt; 데이터를 객체의 필드로 옮기기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. &quot;어떤 객체를 변경할 때 다른 객체에 있는 필드를 변경하는 경우&quot;&lt;/b&gt; 변경의 주체로&amp;nbsp;&lt;span&gt;필드를&lt;/span&gt;&amp;nbsp;옮기기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. &quot;여러 객체에 동일한 필드를 수정하는 경우&quot;&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 class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class Customer {

    private String name;

    private double discountRate;

    private CustomerContract contract;

    public Customer(String name, double discountRate) {
        this.name = name;
        this.discountRate = discountRate;
        this.contract = new CustomerContract(dateToday());
    }

    public double getDiscountRate() {
        return discountRate;
    }

    public void becomePreferred() {
        this.discountRate += 0.03;
    }

    public double applyDiscount(double amount) {
        BigDecimal value = BigDecimal.valueOf(amount);
        return value.subtract(value.multiply(BigDecimal.valueOf(this.discountRate))).doubleValue();
    }

    private LocalDate dateToday() {
        return LocalDate.now();
    }
}

public class CustomerContract {

    private LocalDate startDate;

    public CustomerContract(LocalDate startDate) {
        this.startDate = startDate;
    }
}&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;단순히 필드를 옮기는 것이 아니라, 필드와 관련된 메서드 가령 becomePreferred의 책임을 누가 지게 될 것인지 고민이 필요한 부분이다. 본인의 경우 CustomerContract가 그 책임을 지게 했고 판단에 따라 Customer의 책임으로 볼 수 도 있다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class Customer {

    private String name;

    private CustomerContract contract;

    public Customer(String name, double discountRate) {
        this.name = name;
        this.contract = new CustomerContract(dateToday(), discountRate);
    }

    public double getDiscountRate() {
        return contract.getDiscountRate();
    }

    public void becomePreferred() {
        contract.becomePreferred(0.03);
    }

    public double applyDiscount(double amount) {
        BigDecimal value = BigDecimal.valueOf(amount);
        return value.subtract(value.multiply(BigDecimal.valueOf(getDiscountRate()))).doubleValue();
    }

    private LocalDate dateToday() {
        return LocalDate.now();
    }
}

@Data
public class CustomerContract {

    private LocalDate startDate;

    private double discountRate;

    public CustomerContract(LocalDate startDate, double discountRate) {
        this.startDate = startDate;
        this.discountRate = discountRate;
    }

    public void becomePreferred(double discountRate) {
        this.discountRate += discountRate;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;&lt;span&gt; &amp;nbsp; 함수 인라인&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;'함수 인라인'이란, 일련의 코드를 메서드로 추출해서 메서드의 이름으로 의도를 표현하는 '함수 추출하기'의 반대되는, &lt;/span&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 data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Student의 지각 횟수를 보고 페널티를 계산하는 StudentService에서 불필요하게 우회하는 메서드를 인라인화 시킬 수 있다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@AllArgsConstructor
@Getter
public class Student {

    private String name;

    // 지각 횟수
    private int numberOfTardy;
}

public class StudentService {

    public int getTardyPenalty(Student student) {
        return isTardyMoreThanFive(student) ? 20 : 0;
    }

    private boolean isTardyMoreThanFive(Student student) {
        return student.getNumberOfTardy() &amp;gt; 5;
    }
}&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;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;// after
public class StudentService {

    public int getTardyPenalty(Student student) {
        return student.getNumberOfTardy() &amp;gt; 5 ? 20 : 0;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;&lt;span&gt;  클래스 인라인&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;아래 예제에서 TrackingInformation이 구조가 빈약한 형태의 클래스로, Shipment로 클래스 인라인을 적용해 볼 만하다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Data
public class Shipment {

    private TrackingInformation trackingInformation;

    public Shipment(TrackingInformation trackingInformation) {
        this.trackingInformation = trackingInformation;
    }

    public String getTrackingInfo() {
        return this.trackingInformation.display();
    }
}

public class TrackingInformation {

    private String shippingCompany;

    private String trackingNumber;

    public String display() {
        return this.shippingCompany + &quot;: &quot; + this.trackingNumber;
    }
}&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;Shipment로 &lt;span&gt;TrackingInformation의 모든 필드와 메서드를 옮겼다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@AllArgsConstructor
public class Shipment {

    private String shippingCompany;

    private String trackingNumber;

    public String getTrackingInfo() {
        return shippingCompany + &quot;: &quot; + trackingNumber;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>리팩토링</category>
      <author>Kangworld</author>
      <guid isPermaLink="true">https://kangworld.tistory.com/301</guid>
      <comments>https://kangworld.tistory.com/301#entry301comment</comments>
      <pubDate>Thu, 22 Dec 2022 00:00:26 +0900</pubDate>
    </item>
    <item>
      <title>[리팩토링] 악취 7 : 뒤엉킨 변경</title>
      <link>https://kangworld.tistory.com/300</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;refactoring.jpg&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;515&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cfreQt/btrTYC62Z2n/Wk16bWbxaj76kfMrCEGbo0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cfreQt/btrTYC62Z2n/Wk16bWbxaj76kfMrCEGbo0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cfreQt/btrTYC62Z2n/Wk16bWbxaj76kfMrCEGbo0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcfreQt%2FbtrTYC62Z2n%2FWk16bWbxaj76kfMrCEGbo0%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;600&quot; height=&quot;386&quot; data-filename=&quot;refactoring.jpg&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;515&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;h1&gt;&lt;span&gt;✍️ 악취 7 : 뒤엉킨 변경&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋은 코드는 응집도는 높고 결합도는 낮아야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;응집도는 관련 있는 데이터 혹은 기능이 한 곳에 잘 밀집되어 있는가를 말하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결합도는 상호간 의존하는 정도를 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가령 서로 연관이 없는 A, B 모듈이 수정됐을 때, 양쪽에서 모두 C 모듈의 수정을 요구한다면 응집도가 낮고 결합도는 높다고 할 수 있다. 이처럼 한 모듈이 여러 가지 이유로 수정되어야 한다면 역할이 제대로 나눠지지 않았을 수 있다. 서로 다른 문제는 서로 다른 모듈에서 해결해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뒤엉킨 변경을 해결하기 위한 세 가지 리팩토링 기법이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. &quot;단계 쪼개기&quot; 서로 다른 문맥의 코드를 분리&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. &quot;함수 옮기기&quot; 적절한 모듈로 함수를 옮기기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. &quot;클래스 추출하기&quot; 모듈이 클래스 단위라면&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;  단계 쪼개기&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서로 다른 일을 하는 코드라면 각기 다른 모듈 혹은 클래스 혹은 메서드로 분리한다. 그래야만 수정사항이 생겼을 때 그것만 신경 쓸 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 일을 하는 메서드의 경우 처리 과정을 각기 다른 단계로 구분할 수 있다. 가령 1.전처리 작업 2.메인 작업 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;여기 상품의 가격을 계산하고 할인가를 계산하고 가격에 따른 운임료를 계산해서 최종 비용을 구하는 메서드가 있다. 아래 메서드를 크게 세 부분으로 나눌 수 있는데, 1. 수량에 따른 가격을 계산하고 수량에 따른 할인을 계산한다. 2. 가격에 따라 운임료를 결정한다. 3. 최종 비용을 계산한다.&lt;/p&gt;
&lt;pre class=&quot;fortran&quot;&gt;&lt;code&gt;public class PriceOrder {

    public double priceOrder(Product product, int quantity, ShippingMethod shippingMethod) {
        // 1. 가격, 할인 계산
        final double basePrice = product.basePrice() * quantity;
        final double discount = Math.max(quantity - product.discountThreshold(), 0)
                * product.basePrice() * product.discountRate();

        // 2. 운임료 계산
        final double shippingPerCase = (basePrice &amp;gt; shippingMethod.discountThreshold()) ?
                shippingMethod.discountedFee() : shippingMethod.feePerCase();
        final double shippingCost = quantity * shippingPerCase;

        // 3. 가격 - 할인 + 운임료 계산
        final double price = basePrice - discount + shippingCost;
        return price;
    }
}&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 class=&quot;reasonml&quot;&gt;&lt;code&gt;public class PriceOrder {

    public double priceOrder(Product product, int quantity, ShippingMethod shippingMethod) {
        // 1. 가격과 할인율 계산
        final double basePrice = product.basePrice() * quantity;
        final double discount = Math.max(quantity - product.discountThreshold(), 0)
                * product.basePrice() * product.discountRate();

        // 2. 운임료 계산
        final PriceData priceData = new PriceData(basePrice, quantity);
        final double shippingCost = calcShippingCost(priceData, shippingMethod);

        // 3. 가격 - 할인 + 운임료 계산
        final double price = basePrice - discount + shippingCost;
        return price;
    }

    private double calcShippingCost(PriceData priceData, ShippingMethod shippingMethod) {
        final double shippingPerCase = (priceData.basePrice() &amp;gt; shippingMethod.discountThreshold()) ?
                shippingMethod.discountedFee() : shippingMethod.feePerCase();

        return priceData.quantity() * shippingPerCase;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;&lt;span&gt; &amp;nbsp; 함수 옮기기&lt;/span&gt;&lt;/h1&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;한 메서드가 다른 클래스의 필드를 더 많이 참조하거나, 다른 클래스에서도 메서드를 필요로 하는 경우가 그러하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Subscriber 클래스는 구독 기간과 등급을 표현하는 필드와 기간과 등급에 따른 월간 구독료를 반환하는 메서드를 갖는다. 여기서 메서드 옮기기의 대상은 getDiscount로 등급에 따라 분기를 처리하고 있기에 그 책임을 Grade 내부로 옮겨볼 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public enum Grade {
    VIP, VVIP;
}

@AllArgsConstructor
@Getter
public class Subscriber {

    private int subscriptionPeriod;
    private Grade grade;

    public int getMonthlyFee() {
        int fee = 10000;

        if (subscriptionPeriod &amp;gt;= 6)
            fee -= getDiscount();

        return fee;
    }

    private int getDiscount() {
        if (grade == Grade.VVIP) {
            if (subscriptionPeriod &amp;lt;= 10)
                return 2000;

            return 4000;
        }

        return 1000;
    }
}&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;할인 금액 계산 코드를 Grade로 옮기긴 했지만 실습의 의미가 크고 &lt;span&gt;Subscriber&lt;span&gt; 클래스에 있어도 문제없다고 생각한다. 다만 &lt;span&gt;getDiscount&lt;/span&gt;에서 Subscriber의 필드를 더 많이 참조해야 한다면 원래 위치로 옮기는 것이 좋을듯하다. 무엇보다 getDiscount에 Subscriber를 넘기면 &lt;span&gt;쌍방 참조가 생기므로 그것만은&lt;/span&gt;&amp;nbsp;피하자.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public enum Grade {
    VIP, VVIP;

    public int getDiscount(int subscriptionPeriod) {
        if (this == Grade.VVIP) {
            if (subscriptionPeriod &amp;lt;= 10)
                return 2000;

            return 4000;
        }

        return 1000;
    }
}

@AllArgsConstructor
@Getter
public class Subscriber {

    private int subscriptionPeriod;
    private Grade grade;

    public int getMonthlyFee() {
        int fee = 10000;

        if (subscriptionPeriod &amp;gt;= 6)
            fee -= grade.getDiscount(subscriptionPeriod);

        return fee;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;&lt;span&gt;  클래스 추출하기&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스가 다루는 책임이 많아질수록 클래스가 점차 커짐에 따라 클래스를 쪼갤 필요가 있다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Person 클래스는 이름과 사무실 지역번호, 사무실 번호를 필드로 갖는다.&lt;br /&gt;여기서 officeAreaCode와 officeNumber가 밀접한 관련이 있는 필드로 간주해서 별도의 클래스로 추출할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;@AllArgsConstructor
@Data
public class Person {

    private String name;

    private String officeAreaCode;
    private String officeNumber;

    public String getTelephoneNumber() {
        return this.officeAreaCode + &quot; &quot; + this.officeNumber;
    }
}
&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 class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@AllArgsConstructor
@Getter
public class TelephoneNumber {

    private String areaCode;
    private String number;

    @Override
    public String toString() {
        return areaCode + &quot; &quot; + number;
    }
}

@AllArgsConstructor
@Getter
public class Person {

    private String name;
    private TelephoneNumber telephoneNumber;

    public String getTelephoneNumber() {
        return telephoneNumber.toString();
    }
}&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>리팩토링</category>
      <author>Kangworld</author>
      <guid isPermaLink="true">https://kangworld.tistory.com/300</guid>
      <comments>https://kangworld.tistory.com/300#entry300comment</comments>
      <pubDate>Mon, 19 Dec 2022 23:20:46 +0900</pubDate>
    </item>
    <item>
      <title>[Java] Enum을 활용하는 방법</title>
      <link>https://kangworld.tistory.com/299</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cBZJzD/btrS0fRailn/tbhLGmBYYoLPGiJiUBmvwK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cBZJzD/btrS0fRailn/tbhLGmBYYoLPGiJiUBmvwK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cBZJzD/btrS0fRailn/tbhLGmBYYoLPGiJiUBmvwK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcBZJzD%2FbtrS0fRailn%2FtbhLGmBYYoLPGiJiUBmvwK%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;400&quot; height=&quot;250&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&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;h1&gt;&lt;span&gt;✍️ 서론&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;논리적으로 연관된 상수를 정의할 때 사용하는 자료형 enum.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;enum은 심볼릭하게 그 자체로도 의미를 가지지만 &lt;span style=&quot;letter-spacing: 0px;&quot;&gt;필요에 따라 &lt;/span&gt;&lt;b&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;필드를 추가하거나&amp;nbsp; &lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;&lt;b&gt;메서드를 추상화&lt;/b&gt;해서 enum이 책임질 수 있는 코드를 구현할 수 있다.&lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;  simple enum&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;enum의 가장 단순한 사용법으로 보통 상태 값이나, 분기를 위한 플래그로 사용된다.&lt;/p&gt;
&lt;pre class=&quot;crystal&quot;&gt;&lt;code&gt;public enum Operator {
    PLUS,
    MINUS,
    MULTIPLY,
    DIVIDE;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class Calculator {

    public double calculate(double a, double b, Operator op) {
        if (op == Operator.PLUS) {
            return a + b;

        } else if (op == Operator.MINUS) {
            return a - b;

        } else if (op == Operator.MULTIPLY) {
            return a * b;

        } else if (op == Operator.DIVIDE) {
            return a / b;
        }

        throw new IllegalArgumentException(&quot;Illegal Operator : &quot; + op);
    }
}

class CalculatorTest {

    @Test
    public void test_calculate() {
        Calculator calculator = new Calculator();
        double res = calculator.calculate(1.0, 2.0, Operator.PLUS);
        assertEquals(res, 3.0);
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;&lt;span&gt;  field&amp;nbsp;&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;enum은 필드를 가질 수 있다. 필드에 함수형 인터페이스를 선언하고 &lt;span&gt;Java 8에 추가된 람다 표현식을 활용하면 코드가 간결해지고 계산이란 행위를 enum에서 책임질 수 있게 된다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@AllArgsConstructor
public enum Calculator {
    PLUS((a, b) -&amp;gt; a + b),
    MINUS((a, b) -&amp;gt; a - b),
    MULTIPLY((a, b) -&amp;gt; a * b),
    DIVIDE((a, b) -&amp;gt; a / b);

    private BinaryOperator&amp;lt;Double&amp;gt; binaryOperator;

    public double calculate(double a, double b) {
        return binaryOperator.apply(a, b);
    }
}

class CalculatorTest {

    @Test
    public void test_calculator() {
        Calculator calculator = Calculator.PLUS;
        double res = calculator.calculate(1.0, 2.0);

        assertEquals(res, 3.0);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;&lt;span&gt;  abstract method&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;enum에 추상 메서드를 추가해서 상수가 가진 의미에 따라 메서드를 구현하도록 유도할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public enum Calculator {

    PLUS {
        @Override
        public double calculate(double a, double b) {
            return a + b;
        }
    },
    MINUS {
        @Override
        public double calculate(double a, double b) {
            return a - b;
        }
    },
    MULTIPLY {
        @Override
        public double calculate(double a, double b) {
            return a * b;
        }
    },
    DIVIDE {
        @Override
        public double calculate(double a, double b) {
            return a / b;
        }
    };

    public abstract double calculate(double a, double b);
}

class CalculatorTest {

    @Test
    public void test_calculator() {
        Calculator calculator = Calculator.PLUS;
        double res = calculator.calculate(1.0, 2.0);

        assertEquals(res, 3.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;혹은 추상 메서드가 아닌 일반 메서드를 정의하고, 특정 상수에서 재정의 할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public enum Grade {

    GOLD,
    VIP,
    VVIP {
        @Override
        public double discountRate() {
            return 0.2;
        }
    };

    public double discountRate() {
        return 0.1;
    }
}

class GradeTest {

    @Test
    public void test_grade() {
        assertEquals(Grade.GOLD.discountRate(), 0.1);
        assertEquals(Grade.VIP.discountRate(), 0.1);
        assertEquals(Grade.VVIP.discountRate(), 0.2);
    }
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Java</category>
      <author>Kangworld</author>
      <guid isPermaLink="true">https://kangworld.tistory.com/299</guid>
      <comments>https://kangworld.tistory.com/299#entry299comment</comments>
      <pubDate>Tue, 6 Dec 2022 23:56:35 +0900</pubDate>
    </item>
    <item>
      <title>[리팩토링] 악취 6 : 가변 데이터</title>
      <link>https://kangworld.tistory.com/297</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;refactoring.jpg&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;515&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bTyrnG/btrSasj68lU/25biZsUiCeyIhJ540sdeC0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bTyrnG/btrSasj68lU/25biZsUiCeyIhJ540sdeC0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bTyrnG/btrSasj68lU/25biZsUiCeyIhJ540sdeC0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbTyrnG%2FbtrSasj68lU%2F25biZsUiCeyIhJ540sdeC0%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;600&quot; height=&quot;386&quot; data-filename=&quot;refactoring.jpg&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;515&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;h1&gt;&lt;span&gt;✍️ 악취 6 : 가변 데이터&lt;/span&gt;&lt;/h1&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;1. &lt;b&gt;&quot;변수 쪼개기&quot;&lt;/b&gt; 여러 데이터를 하나의 변수로 관리하지 않기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. &lt;b&gt;&quot;질의 메서드와 변경 메서드 분리하기&quot;&lt;/b&gt; 사이트 이펙트가 있는 메서드와 없는 메서드를 분리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. &lt;b&gt;&quot;세터 제거하기&quot;&lt;/b&gt; 필요한 경우에만 세터를 만들기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. &lt;b&gt;&quot;파생 변수를 질의 메서드로&quot;&lt;/b&gt; 계산해서 알아낼 수 있는 값이라면 변수가 아닌 질의 메서드 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. &lt;b&gt;&quot; 참조를 값으로 바꾸기&quot;&lt;/b&gt; 데이터를 일부만 변경하기보단 새로운 불변 객체 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;  변수 쪼개기&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;어떤 변수가 여러 번 재할당 되어도 적절한 경우가 있다. 가령 반복문에서 순회에 사용되는 인덱스 변수 혹은 누적값에 사용되는 변수가 그러하다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;// 이런 경우 OK!
public static void main(String[] args) {

    int sum = 0;
    for (int i = 0; i &amp;lt; 10; i++) {
        sum += i;
    }
}&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;color: #ee2323;&quot;&gt;&lt;b&gt;변수를 여러 용도로 사용하는 것보단&lt;/b&gt;&lt;/span&gt; &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;하나의 변수는 하나의 책임을 지도록&lt;/b&gt;&lt;/span&gt; 만드는 것이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기 지불 비용인 totalPrice에 계속해서 변화를 주는 것보단&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class Order {

    public double discount(double totalPrice, int quantity) {
        // 만원 이상 구매시 10% 할인
        if (totalPrice &amp;gt;= 10000)
            totalPrice = totalPrice * 0.9;

        // 10개 이상 구매시에도 10% 할인
        if (quantity &amp;gt;= 10)
            totalPrice = totalPrice * 0.9;

        return totalPrice;
    }
}&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;반환용 변수를 만들고 초기 지불 비용인 totalPrice의 값은 메서드 내에서 유지하는 것이 좋다.&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public double discount(double totalPrice, int quantity) {
    double result = totalPrice;

    // 만원 이상 구매시 10% 할인
    if (totalPrice &amp;gt;= 10000)
        result *= 0.9;

    // 10개 이상 구매시에도 10% 할인
    if (quantity &amp;gt;= 10)
        result *= 0.9;

    return result;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;&lt;span&gt; &amp;nbsp; 질의 메서드와 변경 메서드 분리&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;질의 메서드와 변경 메서드 분리, 쉽게 말하면 getter와 setter를 명백히 분리하는 리팩토링 기법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 값을 반환하는 getter 메서드는 사이트 이펙트가 없어야 한다. 가령 단순 조회를 위해 메서드를 호출했는데 메서드 내부에서 특정 변수의 값이 변경된다면 사용하는 입장에서 메서드 호출로 인한 변수의 변경을 예측하기 어렵다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기 예외 목록을 제외한 임의의 정수를 뽑는 nextInt 메서드가 있다. 로직은 단순하다. excludes를 제외한 범위 내의 임의의 난수를 뽑고 excludes에 set하고 값을 반환한다. 한 메서드에 get과 set이 모두 포함된 케이스이다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class RandomService {

    public int nextInt(int bound, Set&amp;lt;Integer&amp;gt; excludes) {
        List&amp;lt;Integer&amp;gt; candidates = IntStream.range(0, bound)
                .boxed()
                .filter(i -&amp;gt; !excludes.contains(i))
                .collect(Collectors.toList());

        Random rand = new Random();
        int randIdx = rand.nextInt(candidates.size());
        int randValue = candidates.get(randIdx);

        excludes.add(randValue);

        return randValue;
    }
}

class RandomServiceTest {

    @Test
    public void nextInt() {
        RandomService randomService = new RandomService();
        Set&amp;lt;Integer&amp;gt; excludes = new HashSet&amp;lt;&amp;gt;();

        randomService.nextInt(10, excludes);
        randomService.nextInt(10, excludes);
    }

}&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;nextInt 메서드는 순수 getter로 만들고 excludes에 add하는 setter 코드는 외부 혹은 별도의 메서드로 정의하는 것이 변수가 변경되는 흐름을 파악하기 쉽다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class RandomService {

    public int nextInt(int bound, Set&amp;lt;Integer&amp;gt; excludes) {
        List&amp;lt;Integer&amp;gt; candidates = IntStream.range(0, bound)
                .boxed()
                .filter(i -&amp;gt; !excludes.contains(i))
                .collect(Collectors.toList());

        Random rand = new Random();
        int randIdx = rand.nextInt(candidates.size());

        return candidates.get(randIdx);
    }
}

class RandomServiceTest {

    @Test
    public void nextInt() {
        RandomService randomService = new RandomService();
        Set&amp;lt;Integer&amp;gt; excludes = new HashSet&amp;lt;&amp;gt;();

        int randValue = randomService.nextInt(10, excludes);
        excludes.add(randValue);
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;&lt;span&gt;  세터 제거하기&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;어떤 객체가 생성된 이후 값이 변경되지 않아야 한다면 불필요한 setter를 제거해서 &lt;b&gt;불변 객체(Immutable Object)&lt;/b&gt;로 만들 필요가 있다. &lt;/span&gt;객체지향에서 불변 객체라 함은, 초깃값을 설정할 생성자와 이후에 값을 얻어올 getter 메서드만 있을 때 불변 객체라 한다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@AllArgsConstructor
@Getter
public class Person {

    // 주민 등록 번호
    private int pid;

    // 생년월일
    private DateTime birthDate;
}

public class App {

    public static void main(String[] args) {
        Person person = new Person(1, new DateTime(2022, 1, 1, 0, 0, 0));

        // only get, no set
        person.getPid();
        person.getBirthDate();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;&lt;span&gt; ️ 파생 변수를 질의 메서드로 바꾸기&lt;/span&gt;&lt;/h1&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;discountedPrice는 setDiscount 메서드가 호출되기 전까진 계산되지 않는 파생 변수이다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class Product {

    private int price;
    private int discount;

    // 파생 변수
    private int discountedPrice;

    public Product(int price) {
        this.price = price;
    }

    public int getDiscountedPrice() {
        return discountedPrice;
    }

    public void setDiscount(int discount) {
        this.discount = discount;
        this.discountedPrice = Math.max(this.price - this.discount, 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;파생 변수는 소스가 되는 변수에 종속적인 경향이 있기에 아래 테스트 코드처럼 setDiscount전에 getDiscountedPrice를 호출하면 버그가 발생한다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;class ProductTest {

    @Test
    public void test_O() {
        Product product = new Product(10000);

        product.setDiscount(1000);

        assertEquals(9000, product.getDiscountedPrice());
    }

    @Test
    public void test_X() {
        Product product = new Product(10000);

        // error not equals !!
        assertEquals(10000, product.getDiscountedPrice());
    }

}&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;다른 변수에 종속적인 파생 변수를&lt;/span&gt; 질의 메서드로 변경해서 가변 데이터를 제거할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;public class Product {

    private int price;
    private int discount;

    public Product(int price) {
        this.price = price;
    }

    public int getDiscountedPrice() {
        return Math.max(this.price - this.discount, 0);
    }

    public void setDiscount(int discount) {
        this.discount = discount;
    }

}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;&lt;span&gt;✨ 참조를 값으로 바꾸기&lt;/span&gt;&lt;/h1&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 class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@AllArgsConstructor
@Data
@ToString
public class Stock {

    private DateTime time;
    private int id;
    private String name;
    private int price;
}

public class Buyer {

    private List&amp;lt;Stock&amp;gt; purchaseList = new ArrayList&amp;lt;&amp;gt;();

    public void buy(Stock stock) {
        purchaseList.add(stock);
    }

    public void print() {
        purchaseList.forEach(System.out::println);
    }
}&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 class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public static void main(String[] args) throws InterruptedException {
    DateTime now = new DateTime(2000, 1, 1, 0, 0, 0);
    Stock samsung = new Stock(now, 1, &quot;samsung&quot;, 100);

    Buyer buyer = new Buyer();
    buyer.buy(samsung);

    Thread.sleep(1);

    samsung.setTime(now.plusSeconds(1));
    samsung.setPrice(200);

    buyer.buy(samsung);

    buyer.print();
    // Stock(time=2000-01-01T00:00:01.000+09:00, id=1, name=samsung, price=200)
    // Stock(time=2000-01-01T00:00:01.000+09:00, id=1, name=samsung, price=200)

}&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 class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@EqualsAndHashCode(of = {&quot;id&quot;, &quot;time&quot;})
@AllArgsConstructor
@Getter
@ToString
public class Stock {

    private DateTime time;
    private int id;
    private String name;
    private int price;
}

public static void main(String[] args) throws InterruptedException {
    DateTime now = new DateTime(2000, 1, 1, 0, 0, 0);
    Stock samsung = new Stock(now, 1, &quot;samsung&quot;, 100);

    Buyer buyer = new Buyer();
    buyer.buy(samsung);

    Thread.sleep(1);

    samsung = new Stock(now.plusSeconds(1), samsung.getId(), samsung.getName(), 200);
    buyer.buy(samsung);

    buyer.print();
    // Stock(time=2000-01-01T00:00:00.000+09:00, id=1, name=samsung, price=100)
    // Stock(time=2000-01-01T00:00:01.000+09:00, id=1, name=samsung, price=200)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>리팩토링</category>
      <author>Kangworld</author>
      <guid isPermaLink="true">https://kangworld.tistory.com/297</guid>
      <comments>https://kangworld.tistory.com/297#entry297comment</comments>
      <pubDate>Sun, 27 Nov 2022 21:32:29 +0900</pubDate>
    </item>
    <item>
      <title>[리팩토링] 악취 5 : 전역 변수</title>
      <link>https://kangworld.tistory.com/295</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;refactoring.jpg&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;515&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bsU5Gw/btrQ6IUGQ1K/h4R5GB9UF6cCo40FpALt90/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bsU5Gw/btrQ6IUGQ1K/h4R5GB9UF6cCo40FpALt90/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bsU5Gw/btrQ6IUGQ1K/h4R5GB9UF6cCo40FpALt90/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbsU5Gw%2FbtrQ6IUGQ1K%2Fh4R5GB9UF6cCo40FpALt90%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;600&quot; height=&quot;386&quot; data-filename=&quot;refactoring.jpg&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;515&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;h1&gt;&lt;span&gt;✍️ 악취 5 : 전역 변수(전역 데이터)&amp;nbsp;&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전역 변수, 가령 자바의 public static 변수는 어플리케이션 전반에 걸쳐서 접근하고 수정할 수 있기에 어느 위치에서 값이 참조되고 수정되는지 파악하기 어렵다. 이건 비단 전역 변수만의 문제가 아니라 클래스 필드도 그러하다. 클래스 필드가 외부에 public하게 노출되어 있다면 필드의 상태 변경을 파악하기 어려워진다.&lt;/p&gt;
&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: #000000;&quot;&gt;전역 데이터를 위해 리팩토링을 활용할 수 있다.&lt;/span&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;h1 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;  변수 캡슐화하기&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전역 변수와 클래스의 필드가 외부에 public하게 노출되어 참조 위치가 많아지면 상태 값을 추적하기 어렵다. 가령 단순 읽기 위해 변수를 참조했는지 값을 변경하기 위해 참조했는지 파악하기 어렵다. 게다가 변수에 값을 직접 할당하면 값에 대한 검증이 불가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변수의 조회와 변경을 별도의 메서드를 통해서만 가능하게 한다면 의도를 명확하게 분리할 수 있고 앞서 말한 값에 대한 검증 과정도 메서드 내에서 처리할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;// before
public class AirConditioning {

    public static int targetTemperature = 25;

    // 제습 모드
    public static boolean dehumidificationMode = true;

    // 화씨로 표기
    public static boolean readInFahrenheit = false;

}


public class App {

    public static void main(String[] args) {
        System.out.println(AirConditioning.targetTemperature);

        AirConditioning.targetTemperature = -10000;
        AirConditioning.dehumidificationMode = true;
        AirConditioning.readInFahrenheit = 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;전역 변수를 private으로 변경하고 각각의 getter/setter로만 접근하도록 변경했다. 변수 사용 시 의도를 명확하게 분리할 수 있고 메서드 내부에 로직을 추가해서 코드의 응집력을 높이는 효과도 있다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;// after
public class AirConditioning {

    private static int targetTemperature = 25;

    // 제습 모드
    private static boolean dehumidificationMode = true;

    // 화씨로 표기
    private static boolean readInFahrenheit = false;

    public static Integer getTargetTemperature() {
        if (readInFahrenheit)
            return (int) (targetTemperature * 1.8 + 32);

        return targetTemperature;
    }

    public static void setTargetTemperature(Integer targetTemperature) {
        if (targetTemperature &amp;gt; 40 || targetTemperature &amp;lt; 10)
            throw new IllegalArgumentException();

        AirConditioning.targetTemperature = targetTemperature;
    }

    ```
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>리팩토링</category>
      <author>Kangworld</author>
      <guid isPermaLink="true">https://kangworld.tistory.com/295</guid>
      <comments>https://kangworld.tistory.com/295#entry295comment</comments>
      <pubDate>Sun, 13 Nov 2022 20:28:48 +0900</pubDate>
    </item>
    <item>
      <title>[리팩토링] 악취 4. 긴 매개변수 목록</title>
      <link>https://kangworld.tistory.com/294</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;refactoring.jpg&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;515&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwgqWm/btrQsEGw1v0/Whw9O5s9JgTG5WeL0UyGbK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwgqWm/btrQsEGw1v0/Whw9O5s9JgTG5WeL0UyGbK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwgqWm/btrQsEGw1v0/Whw9O5s9JgTG5WeL0UyGbK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwgqWm%2FbtrQsEGw1v0%2FWhw9O5s9JgTG5WeL0UyGbK%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;600&quot; height=&quot;386&quot; data-filename=&quot;refactoring.jpg&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;515&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;h1&gt;&lt;span&gt;✍️ 악취 4 : 긴 매개변수 목록&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메서드의 매개변수가 많을수록 함수의 역할을 이해하기 어렵다. 고려해 볼 사항으로 &lt;b&gt;과연 그 메서드가 한 가지 일을 하고 있는 게 맞는지, 불필요한 매개변수는 없는지, 논리적으로 그룹핑할 매개변수 목록은 없는지 검토해야 한다. &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;긴 매개 변수 목록을 위해 네 가지&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;리팩토링을 활용할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1.&lt;/b&gt; &lt;b&gt;&quot;매개 변수를 질의 함수로 바꾸기&quot;&lt;/b&gt; 만약 어떤 매개변수를 다른 매개변수를 통해 알아낼 수 있다면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2.&lt;/b&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;&quot;플래그 제거하기&quot;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;매개 변수가 플래그로 사용된다면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3.&lt;/b&gt; &lt;b&gt;&quot;객체를 통째로 넘기기&quot; &lt;/b&gt;기존 자료구조에서 세부적인 데이터를 가져와서 플랫하게 넘기는 대신&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4.&lt;/b&gt; &lt;b&gt;&quot;그룹핑해서 매개변수 객체 만들기&quot; &lt;/b&gt;일부 매개변수들이 반복적으로 넘겨진다면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;  매개 변수를 질의 함수로 바꾸기&lt;/span&gt;&lt;/h1&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 class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;// Before
public class Order {

    private int quantity;
    private double itemPrice;

    public Order(int quantity, double itemPrice) {
        this.quantity = quantity;
        this.itemPrice = itemPrice;
    }

    /**
     * 비용 계산 후 수량에 따른 할인 레벨을 결정하고, 할인된 가격 반환 
     * @return
     */
    public double finalPrice() {
        double basePrice = this.quantity * this.itemPrice;
        int discountLevel = this.quantity &amp;gt; 100 ? 2 : 1;

        return discountedPrice(basePrice, discountLevel);
    }

    /**
     * 할인 레벨에 따른 할인가격 계산
     */
    private double discountedPrice(double basePrice, int discountLevel) {
        return discountLevel == 2 ? basePrice * 0.90 : basePrice * 0.95;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;// After
public class Order {

    private int quantity;
    private double itemPrice;

    public Order(int quantity, double itemPrice) {
        this.quantity = quantity;
        this.itemPrice = itemPrice;
    }

    public double finalPrice() {
        double basePrice = this.quantity * this.itemPrice;
        return discountedPrice(basePrice);
    }

    // discountLevel 계산을 discountedPrice의 책임으로 변경함
    private double discountedPrice(double basePrice) {
        return discountLevel() == 2 ? basePrice * 0.90 : basePrice * 0.95;
    }

    private int discountLevel() {
        return this.quantity &amp;gt; 100 ? 2 : 1;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;&lt;span&gt; &amp;nbsp; 플래그 인수 제거하기&lt;/span&gt;&lt;/h1&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;가령 아래 메서드는 어떤 일을 하는지 짐작이 가지만&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;playGame(players);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;플래그 인자가 추가된 순간 내부 구현을 찾아보기 전까진 &lt;span&gt;차이점이 무엇인지 알 수 없다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;playGame(players, true);
playGame(players, 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;'조건문 분해하기'를 활용하면 플래그 인수를 제거할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@AllArgsConstructor
@Getter
public class Order {

    private DateTime placedOn;
    private String deliveryAddress;
}

// Before
public class Shipment {

    public DateTime getDeliveryDate(Order order, boolean isRush) {
        if (isRush) {
            int deliveryTime = switch (order.getDeliveryAddress()) {
                case &quot;SEOUL&quot; -&amp;gt; 1;
                case &quot;INCHEON&quot; -&amp;gt; 2;
                default -&amp;gt; 3;
            };

            return order.getPlacedOn().plusDays(deliveryTime);

        } else {
            int deliveryTime = switch (order.getDeliveryAddress()) {
                case &quot;SEOUL&quot; -&amp;gt; 2;
                case &quot;INCHEON&quot; -&amp;gt; 3;
                default -&amp;gt; 3;
            };

            return order.getPlacedOn().plusDays(deliveryTime);
        }
    }
}

@Test
void deliveryDate() {
    DateTime placedOn = new DateTime(2021, 12, 15, 0, 0);
    Order order = new Order(placedOn, &quot;SEOUL&quot;);

    Shipment shipment = new Shipment();
    assertEquals(placedOn.plusDays(2), shipment.getDeliveryDate(order, false));
    assertEquals(placedOn.plusDays(1), shipment.getDeliveryDate(order, true));
}&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;switch문 로직을 메서드로 빼면 다음과 같은 형태가 되고&lt;/p&gt;
&lt;pre class=&quot;axapta&quot;&gt;&lt;code&gt;public class Shipment {

    public DateTime getDeliveryDate(Order order, boolean isRush) {
        if (isRush) {
            return getRushDeliveryDate(order);

        } else {
            return getRegularDeliveryDate(order);
        }
    }

    private DateTime getRushDeliveryDate(Order order) {
        int deliveryTime = switch (order.getDeliveryAddress()) {
            case &quot;SEOUL&quot; -&amp;gt; 1;
            case &quot;INCHEON&quot; -&amp;gt; 2;
            default -&amp;gt; 3;
        };

        return order.getPlacedOn().plusDays(deliveryTime);
    }

    private DateTime getRegularDeliveryDate(Order order) {
        int deliveryTime = switch (order.getDeliveryAddress()) {
            case &quot;SEOUL&quot; -&amp;gt; 2;
            case &quot;INCHEON&quot; -&amp;gt; 3;
            default -&amp;gt; 3;
        };

        return order.getPlacedOn().plusDays(deliveryTime);
    }
}
&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 class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class Shipment {

    public DateTime getRushDeliveryDate(Order order) {
        int deliveryTime = switch (order.getDeliveryAddress()) {
            case &quot;SEOUL&quot; -&amp;gt; 1;
            case &quot;INCHEON&quot; -&amp;gt; 2;
            default -&amp;gt; 3;
        };

        return order.getPlacedOn().plusDays(deliveryTime);
    }

    public DateTime getRegularDeliveryDate(Order order) {
        int deliveryTime = switch (order.getDeliveryAddress()) {
            case &quot;SEOUL&quot; -&amp;gt; 2;
            case &quot;INCHEON&quot; -&amp;gt; 3;
            default -&amp;gt; 3;
        };

        return order.getPlacedOn().plusDays(deliveryTime);
    }
}

@Test
void deliveryDate() {
    DateTime placedOn = new DateTime(2021, 12, 15, 0, 0);
    Order order = new Order(placedOn, &quot;SEOUL&quot;);

    Shipment shipment = new Shipment();
    assertEquals(placedOn.plusDays(2), shipment.getRegularDeliveryDate(order));
    assertEquals(placedOn.plusDays(1), shipment.getRushDeliveryDate(order));
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;&lt;span&gt; &amp;nbsp; 여러 함수를 클래스로 묶기&lt;/span&gt;&lt;/h1&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;가령 Student 객체를 검증하고 저장하는 로직이 있다고 가정했을 때 validate 메서드들을 하나의 클래스로 빼낼 수 있다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;// Before
public class StudentService {

    private StudentDao studentDao;

    public void saveStudent(Student student) {
        validateName(student.getName(), student.getGender());
        validateScores(student.getKor(), student.getEng(), student.getMath());

        studentDao.insert(student);
    }

    private void validateName(String name, char gender) {
        if (name.isEmpty() || name.length() &amp;gt; 10)
            throw new IllegalArgumentException(&quot;Invalid name length : &quot; + name.length());

        String prefix = &quot;STD_&quot; + gender;
        if (!name.startsWith(prefix))
            throw new IllegalArgumentException(&quot;Student name must start with &quot; + prefix);
    }

    private void validateScores(int kor, int eng, int math) {
        boolean isValid = Arrays.asList(kor, eng, math).stream().noneMatch(v -&amp;gt; v &amp;lt; 0 || v &amp;gt; 100);
        if (!isValid)
            throw new IllegalArgumentException(&quot;Invalid Score&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;// After
public class StudentService {

    private StudentDao studentDao;

    public void saveStudents(Student student) {
        StudentValidator validator = new StudentValidator(student);
        validator.validateName();
        validator.validateScores();

        studentDao.insert(student);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@AllArgsConstructor
public class StudentValidator {

    private Student student;

    public void validateName() {
        String name = student.getName();
        if (name.isEmpty() || name.length() &amp;gt; 10)
            throw new IllegalArgumentException(&quot;Invalid name length : &quot; + name.length());

        String prefix = &quot;STD_&quot; + student.getGender();
        if (!name.startsWith(prefix))
            throw new IllegalArgumentException(&quot;Student name must start with &quot; + prefix);
    }

    public void validateScores() {
        boolean isValid = Arrays.asList(student.getKor(), student.getEng(), student.getMath()).
                stream().noneMatch(v -&amp;gt; v &amp;lt; 0 || v &amp;gt; 100);
        if (!isValid)
            throw new IllegalArgumentException(&quot;Invalid Score&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>리팩토링</category>
      <author>Kangworld</author>
      <guid isPermaLink="true">https://kangworld.tistory.com/294</guid>
      <comments>https://kangworld.tistory.com/294#entry294comment</comments>
      <pubDate>Sun, 6 Nov 2022 22:55:13 +0900</pubDate>
    </item>
    <item>
      <title>[리팩토링] 악취 3. 긴 함수</title>
      <link>https://kangworld.tistory.com/293</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;refactoring.jpg&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;515&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dRxrjc/btrPRdh14n3/VAYO9rMQWwTwnzuGqRSOE1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dRxrjc/btrPRdh14n3/VAYO9rMQWwTwnzuGqRSOE1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dRxrjc/btrPRdh14n3/VAYO9rMQWwTwnzuGqRSOE1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdRxrjc%2FbtrPRdh14n3%2FVAYO9rMQWwTwnzuGqRSOE1%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;600&quot; height=&quot;386&quot; data-filename=&quot;refactoring.jpg&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;515&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;h1&gt;&lt;span&gt;✍️ 악취 : 긴 함수&lt;/span&gt;&lt;b&gt;&lt;/b&gt;&lt;/h1&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;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;리팩토링을 활용할 수 있다.&lt;/span&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;&lt;b&gt;2. 조건문 분해햐기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 반복문 쪼개기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;  함수 추출하기&lt;/span&gt;&lt;/h1&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. 임시 변수를 질의 함수로&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변수를 활용하면 반복되는 동일한 계산을 피할 수 있고 변수의 이름으로 의미를 표현할 수도 있다. 하지만 메서드를 추출할 때 변수는 걸림돌이 될 수 있다. 이때 변수도 마찬가지로 별도의 메서드로 추출한다면 기존에 추출한 메서드의 매개변수를 줄일 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;String으로 빌드 하는 코드를 별도의 메서드로 추출하고 싶다면&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class StudentService {

    /**
     * 학생 성적 요약 출력
     */
    public void printSummary(String name, int kor, int eng, int math) {
        int total = (kor + eng + math);
        double avg = total / 3.0;

        String summary = new StringBuilder()
                .append(name).append(&quot;_&quot;)
                .append(kor).append(&quot;_&quot;)
                .append(eng).append(&quot;_&quot;)
                .append(math).append(&quot;_&quot;)
                .append(avg).toString();
    }
}&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 class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class StudentService {

    public void printSummary(String name, int kor, int eng, int math) {
        String summary = getSummary(name, kor, eng, math);
        System.out.println(summary);
    }

    private String getSummary(String name, int kor, int eng, int math) {
        return new StringBuilder()
                .append(name).append(&quot;_&quot;)
                .append(kor).append(&quot;_&quot;)
                .append(eng).append(&quot;_&quot;)
                .append(math).append(&quot;_&quot;)
                .append(calculateAvg(kor, eng, math)).toString();
    }

    private double calculateAvg(int kor, int eng, int math) {
        int total = (kor + eng + math);

        return total / 3.0;
    }
}&lt;/code&gt;&lt;/pre&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;2. 매개 변수 객체 만들기&lt;/b&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;플랫한 name, kor, eng, math를 하나의 클래스로 묶어서 관리한다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;public class StudentService {

    public void printSummary(Student student) {
        String summary = getSummary(student);
        System.out.println(summary);
    }

    private String getSummary(Student student) {
        return new StringBuilder()
                .append(student.getName()).append(&quot;_&quot;)
                .append(student.getKorean()).append(&quot;_&quot;)
                .append(student.getEnglish()).append(&quot;_&quot;)
                .append(student.getMath()).append(&quot;_&quot;)
                .append(calculateAvg(student)).toString();
    }

    private double calculateAvg(Student student) {
        int total = (student.getKorean() + student.getEnglish() + student.getMath());

        return total / 3.0;
    }
}
&lt;/code&gt;&lt;/pre&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;3. 객체 통째로 넘기기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 클래스에서 구할 수 있는 여러 값을 매개 변수로 전달하는 경우 해당 매개 변수를 하나의 클래스로 교체할 수 있다. 단 플랫한 매개변수를 클래스로 변경했을 때 발생하는 의존성을 고려해야 한다. 그리고 객체를 통째로 넘겨야 한다는 건 어쩌면 &lt;b&gt;메서드의 위치가 적절하지 않아서 생기는 문제일 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 학생의 평균 성적을 구하기 클래스 외부 메서드에 학생 객체를 통째로 넘기고, 해당 메서드에서 평균을 계산하는 것은 객체지향스럽지 않다고 생각한다.&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Getter
public class Student {

    private String name;

    private int korean;
    private int english;
    private int math;

    public double getAvg() {
        return (korean + english + math) / 3.0;
    }
}

public class StudentService {

    public void printSummary(Student student) {
        String summary = getSummary(student);
        System.out.println(summary);
    }

    private String getSummary(Student student) {
        return new StringBuilder()
                .append(student.getName()).append(&quot;_&quot;)
                .append(student.getKorean()).append(&quot;_&quot;)
                .append(student.getEnglish()).append(&quot;_&quot;)
                .append(student.getMath()).append(&quot;_&quot;)
                .append(student.getAvg()).toString();
    }
}&lt;/code&gt;&lt;/pre&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;4. 함수를 명령으로 바꾸기&lt;/b&gt;&lt;/h4&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; 패턴을 적용하면 다음과 같은 장점을 취할 수 있는데, 커맨드 클래스를 통해 복잡한 기능을 구현하는데 필요한 여러 메서드나 필드를 추가할 수 있고 클래스를 사용하므로 상속을 활용해서 확장을 할 수 있다.&amp;nbsp;&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 class=&quot;reasonml&quot;&gt;&lt;code&gt;public class StudentService {

    public void printAndSaveSummary(List&amp;lt;Student&amp;gt; students) {
        // print
        List&amp;lt;String&amp;gt; summaries = students.stream().map(this::getSummary).collect(Collectors.toList());
        summaries.forEach(System.out::println);

        // save
        String fileName = &quot;/summaries.txt&quot;;
        try (FileWriter fileWriter = new FileWriter(fileName)) {
            PrintWriter printWriter = new PrintWriter(fileWriter);
            summaries.forEach(printWriter::print);

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private String getSummary(Student student) {
        return new StringBuilder()
                .append(student.getName()).append(&quot;_&quot;)
                .append(student.getKorean()).append(&quot;_&quot;)
                .append(student.getEnglish()).append(&quot;_&quot;)
                .append(student.getMath()).append(&quot;_&quot;)
                .append(student.getAvg()).toString();
    }
}&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;파일 쓰기 코드를 별도의 SummaryWriter 클래스로 분리했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스로 분리해서 얻어 갈 수 있는 이점으로 파일 쓰기 코드를 더 복잡하게 구성할 수 있고 향후에 엑셀 파일에 쓰기, 콘솔에 쓰기 등 여러 쓰기 기능이 추가된다면 인터페이스를 정의해서 각기 다른 구현체를 만들 수도 있다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class StudentService {

    public void printAndSaveSummary(List&amp;lt;Student&amp;gt; students) {
        // print
        List&amp;lt;String&amp;gt; summaries = students.stream().map(this::getSummary).collect(Collectors.toList());
        summaries.forEach(System.out::println);

        // write 커맨드로 변경
        new SummaryWriter().write(summaries);
    }
    
    ```
}

public class SummaryWriter {

    public void write(List&amp;lt;String&amp;gt; summaries) {
        String fileName = &quot;/summaries.txt&quot;;
        try (FileWriter fileWriter = new FileWriter(fileName)) {
            PrintWriter printWriter = new PrintWriter(fileWriter);
            summaries.forEach(printWriter::print);

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;&lt;span&gt;  조건문 분해하기&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 조건에 따라 달라지는 코드를 작성하다 보면 종종 긴 메서드가 만들어지는 것을 목격할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가령 if문에 명시될 조건 자체가 길어지거나, if와 else에서 해야 할 코드가 길어지는 경우가 그러하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조건문에 명시될 조건 혹은 if-else에 포함될 코드를 메서드로 분해하면 보다 좋은 코드를 작성할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;// Before
public Student findStudent(String name, List&amp;lt;Student&amp;gt; students) {
    Student student = null;
    if (students.stream().anyMatch(s -&amp;gt; s.getName().equals(name))) {
        student = students.stream().
                filter(s -&amp;gt; s.getName().equals(name))
                .findFirst().get();
        
    } else {
        student = new Student(name);
    }

    return student;
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;// After
public Student findStudent(String name, List&amp;lt;Student&amp;gt; students) {
    Optional&amp;lt;Student&amp;gt; optionalStudent = findExistingStudent(name, students);
    if (!optionalStudent.isPresent())
        return new Student(name);

    return optionalStudent.get();
}

private Optional&amp;lt;Student&amp;gt; findExistingStudent(String name, List&amp;lt;Student&amp;gt; students) {
    return students.stream()
            .filter(s -&amp;gt; s.getName().equals(name))
            .findFirst();
}&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;h1&gt;&lt;span&gt;  반복문 쪼개기&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 반복문에서 여러 다른 작업을 하는 코드를 쉽게 찾아볼 수 있다. 만약 해당 반복문을 수정해야 한다면 여러 작업을 모두 고려해서 수정해야 한다. 반복문을 쪼개면 보다 쉽게 이해할 수 있고 쪼개진 반복문을 메서드로 추출하기도 용이하다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;// Before
public void printMinMaxScores(List&amp;lt;Student&amp;gt; students) {
    for (Student student : students) {
        int max = Stream.of(student.getEnglish(), student.getKorean(), student.getMath()).
                max(Comparator.naturalOrder()).get();
        System.out.println(&quot;max :&quot; + max);

        int min = Stream.of(student.getEnglish(), student.getKorean(), student.getMath())
                .min(Comparator.naturalOrder()).get();
        System.out.println(&quot;min :&quot; + min);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1667061735560&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// After
public void printMinMaxScores(List&amp;lt;Student&amp;gt; students) {
    for (Student student : students) {
        int max = Stream.of(student.getEnglish(), student.getKorean(), student.getMath()).
                max(Comparator.naturalOrder()).get();
        System.out.println(&quot;max : &quot; + max);
    }

    for (Student student : students) {
        int min = Stream.of(student.getEnglish(), student.getKorean(), student.getMath()).
                min(Comparator.naturalOrder()).get();
        System.out.println(&quot;min : &quot; + min);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;&lt;span&gt; ️ 조건문을 다형성으로 바꾸기&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;여러 타입에 따라 다른 로직을 처리해야 하는 경우 &lt;b&gt;다형성&lt;/b&gt;을 적용해서 조건문을 보다 명확하게 분리할 수 있다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;가령 반복되는 switch문을 각기 다른 클래스로 만들어 제거할 수 있다. 공통으로 사용되는 로직은 상위 클래스에 두고 달라지는 하위 클래스에 둠으로써 달라지는 부분만 강조할 수 있다.&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;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public enum PrintOrder {
    AVG, KOR, ENG, MATH;
}

public class PrintService {

    public void printStudents(List&amp;lt;Student&amp;gt; students, PrintOrder order) {
        switch (order) {
            case AVG:
                students.stream().
                        sorted(Comparator.comparing(Student::getAvg).reversed())
                        .collect(Collectors.toList()).forEach(System.out::println);
                break;

            case KOR:
                students.stream()
                        .sorted(Comparator.comparing(Student::getKorean).reversed())
                        .collect(Collectors.toList()).forEach(System.out::println);
                break;

            case ENG:
                students.stream()
                        .sorted(Comparator.comparing(Student::getEnglish).reversed())
                        .collect(Collectors.toList()).forEach(System.out::println);
                break;

            case MATH:
                students.stream()
                        .sorted(Comparator.comparing(Student::getMath).reversed())
                        .collect(Collectors.toList()).forEach(System.out::println);
                break;
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;기존의 PrintService를 인터페이스로 변경하고 각 case문에 포함된 로직을 별도의 구현체로 정의한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public interface PrintService {

    void printStudents(List&amp;lt;Student&amp;gt; students);
}

public class AvgOrderPrintService implements PrintService {

    @Override
    public void printStudents(List&amp;lt;Student&amp;gt; students) {
        students.stream().
                sorted(Comparator.comparing(Student::getAvg).reversed())
                .collect(Collectors.toList()).forEach(System.out::println);
    }
}

public class KorOrderPrintService implements PrintService {

    @Override
    public void printStudents(List&amp;lt;Student&amp;gt; students) {
        students.stream()
                .sorted(Comparator.comparing(Student::getKorean).reversed())
                .collect(Collectors.toList()).forEach(System.out::println);
    }
}

```&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;외부에선 별도의 옵션을 넘길 필요 없이 구현체만 변경해서 원하는 형태로 출력할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;public static void main(String[] args) {
    PrintService printService = new AvgOrderPrintService();
    printService.printStudents(Collections.emptyList());

    printService = new KorOrderPrintService();
    printService.printStudents(Collections.emptyList());
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>리팩토링</category>
      <author>Kangworld</author>
      <guid isPermaLink="true">https://kangworld.tistory.com/293</guid>
      <comments>https://kangworld.tistory.com/293#entry293comment</comments>
      <pubDate>Sun, 30 Oct 2022 02:45:33 +0900</pubDate>
    </item>
    <item>
      <title>[리팩토링] 악취 2. 중복 코드</title>
      <link>https://kangworld.tistory.com/292</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;refactoring.jpg&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;515&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tuJH9/btrPQxueKax/67YZzUbKS3CmFPpgubResk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tuJH9/btrPQxueKax/67YZzUbKS3CmFPpgubResk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tuJH9/btrPQxueKax/67YZzUbKS3CmFPpgubResk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtuJH9%2FbtrPQxueKax%2F67YZzUbKS3CmFPpgubResk%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;600&quot; height=&quot;386&quot; data-filename=&quot;refactoring.jpg&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;515&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;h1&gt;&lt;span&gt;✍️ 악취 : 중복 코드&lt;/span&gt;&lt;b&gt;&lt;/b&gt;&lt;/h1&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: #000000;&quot;&gt;세 가지 리팩토링을 활용할 수 있다.&lt;/span&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;&lt;b&gt;2. 코드 정리하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 메서드 올리기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;악취 가득한 코드&lt;/p&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;@AllArgsConstructor
@Getter
public class Customer {

    private int id;

    @Setter
    private String name;
    @Setter
    private String address;

}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@Getter
public class CustomerRepository {

    private List&amp;lt;Customer&amp;gt; customers = new ArrayList&amp;lt;&amp;gt;();

    private void insert(Customer insertObj) {
        Customer exist = customers.stream()
                .filter(c -&amp;gt; c.getId() == insertObj.getId())
                .findAny()
                .orElse(null);

        if (exist != null)
            return;

        customers.add(insertObj);
    }

    private void update(Customer updateObj) {
        Customer exist = customers.stream()
                .filter(c -&amp;gt; c.getId() == updateObj.getId())
                .findAny()
                .orElse(null);

        if (exist == null)
            return;

        exist.setName(updateObj.getName());
        exist.setAddress(updateObj.getAddress());
    }


    public static void main(String[] args) {
        Customer customer = new Customer(1, &quot;Kang&quot;, &quot;seoul&quot;);
        CustomerRepository repository = new CustomerRepository();

        repository.insert(customer);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;  함수 추출하기&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메서드의 이름은 &quot;의도&quot;를 표현한 것이고 메서드의 구현부는 말 그대로 &quot;구현&quot;을 표현한 것이다.&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;insert, update 메서드는 파라미터로 들어온 Customer의 존재 여부를 체크하는 공통된 코드를 가지고 있기에 함수로 추출이 가능해 보인다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Getter
public class CustomerRepository {

    private List&amp;lt;Customer&amp;gt; customers = new ArrayList&amp;lt;&amp;gt;();

    private void insert(Customer insertObj) {
        Optional&amp;lt;Customer&amp;gt; optionalCustomer = findCustomer(insertObj.getId());
        if (optionalCustomer.isPresent())
            return;

        customers.add(insertObj);
    }

    private void update(Customer updateObj) {
        Optional&amp;lt;Customer&amp;gt; optionalCustomer = findCustomer(updateObj.getId());
        if (!optionalCustomer.isPresent())
            return;

        Customer customer = optionalCustomer.get();
        customer.setName(updateObj.getName());
        customer.setAddress(updateObj.getAddress());
    }

    private Optional&amp;lt;Customer&amp;gt; findCustomer(int customerId){
        return customers.stream().filter(c -&amp;gt; c.getId() == customerId).findAny();
    }

    public static void main(String[] args) {
        Customer customer = new Customer(1, &quot;Kang&quot;, &quot;seoul&quot;);
        CustomerRepository repository = new CustomerRepository();

        repository.insert(customer);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;&lt;span&gt;  코드 정리하기&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관련 있는 코드는 묶어서 작성해야 더 쉽게 이해할 수 있다. 가령 메서드에서 사용할 변수를 상단에 미리 정의하기보단 해당 변수를 사용할 코드 바로 위에 선언하자.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public static void main(String[] args) {
    CustomerRepository repository = new CustomerRepository();

    // 선언과 사용을 함께
    Customer customer = new Customer(1, &quot;Kang&quot;, &quot;seoul&quot;);
    repository.insert(customer);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;&lt;span&gt;  메서드 올리기&amp;nbsp;&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중복 코드는 당장은 잘 동작하더라도 미래에 버그를 만들 가능성이 높다. 예를 들어 한 쪽에선 코드를 고치고 다른 한쪽에선 반영하지 않은 경우가 그러하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 여러 하위 클래스에 동일한 코드가 있다면 &lt;b&gt;메서드 올리기&lt;/b&gt;를, 비슷하지만 일부 값만 다르다면 &lt;b&gt;함수 매개변수화&lt;/b&gt;를, 하위 클래스에 있는 코드가 상위 클래스가 아닌 하위 클래스에 의존한다면 &lt;b&gt;필드 올리기&lt;/b&gt;를, 여러 메서드가 비슷한 절차를 밟고 있다면 &lt;b&gt;템플릿 메서드 패턴&lt;/b&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;VipCustomer, VVipCustomer 모두 discounRate를 별도의 멤버로 갖고 있으며 get 메서드도 동일하다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@AllArgsConstructor
public class Customer {

    private int id;
    private String name;
}

public class VipCustomer extends Customer {

    private double discountRate;

    public VipCustomer(int id, String name) {
        super(id, name);
        this.discountRate = discountRate;
    }

    public double getDiscountRate() {
        return discountRate;
    }
}

public class VVipCustomer extends Customer {

    private double discountRate;

    public VVipCustomer(int id, String name, double discountRate) {
        super(id, name);
        this.discountRate = discountRate;
    }

    public double getDiscountRate() {
        return discountRate;
    }
}&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 class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@AllArgsConstructor
public class Customer {

    private int id;
    private String name;
    private double discountRate;

    public double getDiscountRate() {
        return discountRate;
    }
}

public class VipCustomer extends Customer {

    public VipCustomer(int id, String name, double discountRate) {
        super(id, name, discountRate);
    }
}

public class VVipCustomer extends Customer {

    public VVipCustomer(int id, String name, double discountRate) {
        super(id, name, discountRate);
    }
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>리팩토링</category>
      <author>Kangworld</author>
      <guid isPermaLink="true">https://kangworld.tistory.com/292</guid>
      <comments>https://kangworld.tistory.com/292#entry292comment</comments>
      <pubDate>Sat, 29 Oct 2022 16:53:11 +0900</pubDate>
    </item>
    <item>
      <title>[리팩토링] 악취 1. 이해하기 힘든 이름</title>
      <link>https://kangworld.tistory.com/291</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;refactoring.jpg&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;515&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blS5CZ/btrPDruCA8c/EkUhevVzddMMZxpultQTOK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blS5CZ/btrPDruCA8c/EkUhevVzddMMZxpultQTOK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blS5CZ/btrPDruCA8c/EkUhevVzddMMZxpultQTOK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblS5CZ%2FbtrPDruCA8c%2FEkUhevVzddMMZxpultQTOK%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;600&quot; height=&quot;386&quot; data-filename=&quot;refactoring.jpg&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;515&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;h1&gt;&lt;span&gt;✍️ 악취 : 이해하기 힘든 이름&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;깔끔한 코드에서 가장 중요한 것은 &lt;b&gt;좋은 이름&lt;/b&gt;이다.&lt;br /&gt;좋은 이름을 위해 크게 세 가지 리팩토링을 활용할 수 있다.&lt;/span&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;&lt;b&gt; 2. 변수 이름 변경 &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 필드 이름 변경&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;악취 가득한 코드&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@AllArgsConstructor
@Getter
public class Person {

    private String name;
    private int age;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@Getter
public class CustomerRepository {

    private static int sequenceId = 1;

    private List&amp;lt;String&amp;gt; names = new ArrayList&amp;lt;&amp;gt;();
    private List&amp;lt;Integer&amp;gt; ages = new ArrayList&amp;lt;&amp;gt;();
    private List&amp;lt;Integer&amp;gt; ids = new ArrayList&amp;lt;&amp;gt;();

    private void customer(Person person) {
        names.add(person.getName());
        ages.add(person.getAge());
        ids.add(sequenceId++);
    }

    public static void main(String[] args) {
        CustomerRepository repository = new CustomerRepository();

        Person person = new Person(&quot;Kang&quot;, 20);
        repository.customer(person);

        repository.getNames().forEach(System.out::println);
        repository.getAges().forEach(System.out::println);
        repository.getIds().forEach(System.out::println);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;  함수 선언 변경&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋은 이름을 가진 함수는 이름만 보고도 구현을 예상할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋은 이름을 찾는 방법은 함수에 주석을 달고 주석을 함수 이름으로 만들어본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수의 매개 변수는 함수의 문맥을 결정한다. 때로는 정말 필요한 매개변수를 플랫하게 넘겨줄 수 있고, 매개 변수를 포함하는 오브젝트를 넘길 수 있다. 상황에 따라 결정해야 하며 정해진 답은 없다.&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class CustomerRepository {

    private static int sequenceId = 1;

    private List&amp;lt;String&amp;gt; names = new ArrayList&amp;lt;&amp;gt;();
    private List&amp;lt;Integer&amp;gt; ages = new ArrayList&amp;lt;&amp;gt;();
    private List&amp;lt;Integer&amp;gt; ids = new ArrayList&amp;lt;&amp;gt;();

    // customer -&amp;gt; saveCustomer
    private void saveCustomer(Person person) {
        names.add(person.getName());
        ages.add(person.getAge());
        ids.add(sequenceId++);
    }
    
    ```&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;&lt;span&gt;  변수 이름 변경&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변수의 스코프가 넓을수록 이름이 중요하다. 가령 람다식 혹은 루프에서 사용하는 변수는 심플하게 짓는 반면 전역변수는 명확한 의미를 갖도록 작성한다.&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;// name -&amp;gt; customerName
private void saveCustomer(Person customer) {
    names.add(customer.getName());
    ages.add(customer.getAge());
    ids.add(sequenceId++);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;&lt;span&gt;  필드 이름 바꾸기&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스의 필드는 어플리케이션 전반에 걸쳐 참조될 수 있으므로 명확한 의미를 갖도록 작성해야 한다. &lt;b&gt;경우에 따라 관련 있는 필드들은 클래스로 묶어서 관리&lt;/b&gt;할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Getter
public class CustomerRepository {

    private static int sequenceId = 1;

    // names, ages, ids -&amp;gt; customers
    private List&amp;lt;Customer&amp;gt; customers = new ArrayList&amp;lt;&amp;gt;();

    private void saveCustomer(Person customer) {
        customers.add(new Customer(sequenceId++, customer.getName(), customer.getAge()));
    }


    public static void main(String[] args) {
        CustomerRepository repository = new CustomerRepository();

        Person person = new Person(&quot;Kang&quot;, 20);
        repository.saveCustomer(person);

        repository.getCustomers().forEach(System.out::println);
    }

    @AllArgsConstructor
    @Getter
    @ToString
    private static class Customer {
        private int id;
        private String name;
        private int age;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>리팩토링</category>
      <author>Kangworld</author>
      <guid isPermaLink="true">https://kangworld.tistory.com/291</guid>
      <comments>https://kangworld.tistory.com/291#entry291comment</comments>
      <pubDate>Wed, 26 Oct 2022 22:01:46 +0900</pubDate>
    </item>
    <item>
      <title>[Java] JWT (JSON Web Token) 개념 및 예제 코드</title>
      <link>https://kangworld.tistory.com/288</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cGFunO/btrNCfJdPqY/AQL0Ak8F3BRIa6ynVQKvgK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cGFunO/btrNCfJdPqY/AQL0Ak8F3BRIa6ynVQKvgK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cGFunO/btrNCfJdPqY/AQL0Ak8F3BRIa6ynVQKvgK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcGFunO%2FbtrNCfJdPqY%2FAQL0Ak8F3BRIa6ynVQKvgK%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;400&quot; height=&quot;250&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&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;h1&gt;&lt;span&gt;✍️ JWT (JSON Web Token) 개념&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JWT(JSON Web Token)란 &lt;b&gt;선택적 서명 &lt;/b&gt;혹은 &lt;b&gt;선택적 암호화&lt;/b&gt;를 사용해 데이터를 만드는 인터넷 표준으로, &lt;span style=&quot;color: #8a3db6;&quot;&gt;&lt;b&gt;헤더&lt;/b&gt;&lt;/span&gt;, &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;페이로드&lt;/b&gt;&lt;/span&gt;, &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;서명&lt;/b&gt;&lt;/span&gt;으로 구성된다. &lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;페이로드&lt;/span&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;는 클레임(claim)을 담은 JSON 형태이며&lt;/span&gt;&lt;/b&gt; &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;서명은 무결성과 인증을 위해 사용된다&lt;/b&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;146&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;146&quot; data-ke-size=&quot;size16&quot;&gt;쉽게 말하면 JSON 포맷을 이용해서 사용자 정보를 저장하는 Web Token으로, 클라이언트 서버 구조에서 주로 인증과 데이터 전달에 사용된다.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;146&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;146&quot; data-ke-size=&quot;size16&quot;&gt;여기서 &lt;b&gt;선택적 서명과&lt;/b&gt;&amp;nbsp;&lt;b&gt;선택적 암호화&lt;/b&gt;라는 말이 나오는데 JWT에 &lt;span style=&quot;color: #000000;&quot;&gt;서명&lt;/span&gt;을 추가하면 무결성이 보장되는 &lt;b&gt;JWS&lt;/b&gt;가 되고, 암호화를 추가하면 무결성과 기밀성이 보장되는 &lt;b&gt;JWE&lt;/b&gt;가 된다. 비유하자면 JWT를 확장한 클래스가 JWS 혹은 JWE라고 할 수 있다. 하지만 JWT를 이야기할 때 별다른 조건과 설명이 없다면 JWT는 &lt;b&gt;JWS&lt;/b&gt;를 의미한다.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;146&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;  JWT 구조&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #172b4d;&quot;&gt;JWT는 &lt;/span&gt;&lt;b&gt;&lt;span style=&quot;color: #5733b1;&quot;&gt;헤더(Header)&lt;/span&gt;,&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #172b4d;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;페이로드(Payload),&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #172b4d;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;서명(Signature)&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #172b4d;&quot;&gt; 세 파트로 구성되며 각 부분이 &lt;/span&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot; data-renderer-mark=&quot;true&quot; data-text-custom-color=&quot;#bf2600&quot;&gt;Base64&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #172b4d;&quot;&gt;로&lt;/span&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot; data-renderer-mark=&quot;true&quot; data-text-custom-color=&quot;#bf2600&quot;&gt; &lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #172b4d;&quot;&gt;인코딩되어 표현된다. 각각의 파트는 &lt;b&gt;.&lt;/b&gt; 구분자를 통해 식별된다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;250&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/E3Gei/btrNxdZXsVU/4LEfCh5k49VrdqhfmkYIBK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/E3Gei/btrNxdZXsVU/4LEfCh5k49VrdqhfmkYIBK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/E3Gei/btrNxdZXsVU/4LEfCh5k49VrdqhfmkYIBK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FE3Gei%2FbtrNxdZXsVU%2F4LEfCh5k49VrdqhfmkYIBK%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;250&quot; data-origin-width=&quot;700&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. 헤더(Header)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;헤더는 &lt;b&gt;typ&lt;/b&gt;&lt;span&gt;과&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;alg &lt;/b&gt;두 가지 정보를 담고 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;723&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;typ&lt;/b&gt;은 토큰의 타입으로 JWT에 해당한다.&lt;br /&gt;&lt;b&gt;alg&lt;/b&gt;은 해싱 알고리즘을 나타낸다. 해싱 알고리즘으로 보통 SHA, RSA 알고리즘을 사용하며 송신자는 서명을 만들 때, 수신자는 토큰을 검증할 때 헤더에 표기한 해싱 알고리즘을 사용한다.&lt;/p&gt;
&lt;pre id=&quot;code_1664804578507&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{ 
   &quot;alg&quot;: &quot;HS512&quot;,
   &quot;typ&quot;: JWT
 }&lt;/code&gt;&lt;/pre&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;2. 페이로드(Payload)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #172b4d;&quot;&gt;JWT 토큰에서 사용될 정보가 포함된다. 즉, 서버와 클라이언트가 토큰을 주고받을 때 시스템에서 실제로 사용될 정보를 담고 있다. &lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #172b4d;&quot;&gt;여기에 포함되는 정보의 단위를 &lt;/span&gt;&lt;b&gt;Claim&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #172b4d;&quot;&gt;이라고 부르며 Key-Value 타입으로 표현된다. 토큰에 여러 개의 Claim이 포함될 수 있으며 Claim은 크게 세 분류로 나누어져 있다.&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;1. Registered Claim, 등록된 클레임&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. Public Claim, 공개 클레임&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. Private Claim, 비공개 클레임&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;Registered Claim, 등록된 클레임&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #172b4d;&quot;&gt;등록된 클레임은 토큰 자체를 표현하기 위해 미리 정의된 클레임으로 모두 선택적으로 작성이 가능하며 사용할 것을 권장한다. &lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1664805070154&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;iss: 토큰 발급자(issuer)
sub: 토큰 제목(subject)
aud: 토큰 대상자(audience)
exp: 토큰 만료 시간(expiration)
nbf: 토큰 활성 날짜(not before) 
iat: 토큰 발급 시간(issued at)
jti: JWT 토큰 식별자(JWT ID) 중복 방지를 위해 사용하며 일회용 토큰(Access Token) 등에 사용&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;Public Claim, 공개 클레임&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #172b4d;&quot;&gt;공개 클레임은 사용자 정의 클레임으로, 공개용 정보 전달을 위해 사용된다. 충돌 방지를 위해 URI 포맷을 사용한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1664805187014&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{ 
    &quot;https://kangworld.tistory.com/&quot;: true
}&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;Private Claim, 비공개 클레임&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #172b4d;&quot;&gt;비공개 클레임은 사용자 정의 클레임으로, 서버와 클라이언트 간 정보 전달에 사용되는 클레임이다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1664805200619&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{ 
    &quot;userTeamId&quot; : 1,
    &quot;userName&quot; : &quot;kang&quot;
}&lt;/code&gt;&lt;/pre&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;3. 서명(Signature)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #172b4d;&quot;&gt;서명(Signature)은 토큰의 &lt;/span&gt;&lt;b&gt;무결성&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #172b4d;&quot;&gt;을 검증할 때 사용된다. 서명 값을 계산하는 방법으로 &lt;b&gt;1. &lt;/b&gt;헤더(Header)와 페이로드(Playload)를 각각 Base64로 인코딩하고, &lt;b&gt;2. &lt;/b&gt;인코딩 결과들과 비밀키를 헤더에 명시한 해시 알고리즘으로 해싱 한다. &lt;b&gt;3. &lt;/b&gt;마지막으로 이 값을 다시 Base64로 인코딩한다. 과정은 아래 그림을 참고.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;986&quot; data-origin-height=&quot;485&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDo4En/btrNzJcWtrH/mk0J8D5SDsKUETNkgqN4L1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDo4En/btrNzJcWtrH/mk0J8D5SDsKUETNkgqN4L1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDo4En/btrNzJcWtrH/mk0J8D5SDsKUETNkgqN4L1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDo4En%2FbtrNzJcWtrH%2Fmk0J8D5SDsKUETNkgqN4L1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;986&quot; height=&quot;485&quot; data-origin-width=&quot;986&quot; data-origin-height=&quot;485&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;span style=&quot;background-color: #ffffff; color: #172b4d;&quot;&gt;서명이 무결성 검증에 사용되는 시나리오는 간단하다. 엘리스가 밥에게 토큰을 보낸다고 가정해 보자. &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #172b4d;&quot;&gt;엘리스가 보낸 토큰의 &lt;/span&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot; data-renderer-mark=&quot;true&quot; data-text-custom-color=&quot;#0747a6&quot;&gt;헤더가 A&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #172b4d;&quot;&gt;, &lt;/span&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot; data-renderer-mark=&quot;true&quot; data-text-custom-color=&quot;#bf2600&quot;&gt;페이로드는 B&lt;/span&gt; &lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #172b4d;&quot;&gt;일 때 &lt;/span&gt;&lt;b&gt;서명 값은 Hash(&lt;span style=&quot;color: #000000;&quot; data-renderer-mark=&quot;true&quot; data-text-custom-color=&quot;#0747a6&quot;&gt;A&lt;/span&gt; + &lt;span style=&quot;color: #000000;&quot; data-renderer-mark=&quot;true&quot; data-text-custom-color=&quot;#bf2600&quot;&gt;B&lt;/span&gt; + 비밀키)&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #172b4d;&quot;&gt;가 된다. &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #172b4d;&quot;&gt;밥은 엘리스로부터 받은 토큰이 변조되었는지 판별하기 위해 엘리스가 서명을 만든 과정을 동일하게 진행한다. 엘리스로부터 받은 &lt;/span&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot; data-renderer-mark=&quot;true&quot; data-text-custom-color=&quot;#0747a6&quot;&gt;헤더 값 A&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #172b4d;&quot;&gt;, &lt;/span&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot; data-renderer-mark=&quot;true&quot; data-text-custom-color=&quot;#bf2600&quot;&gt;페이로드 값 B&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #172b4d;&quot;&gt; 그리고 비밀키를 이용해서 &lt;/span&gt;&lt;b&gt;Hash(&lt;span style=&quot;color: #000000;&quot; data-renderer-mark=&quot;true&quot; data-text-custom-color=&quot;#0747a6&quot;&gt;A&lt;/span&gt; + &lt;span style=&quot;color: #000000;&quot; data-renderer-mark=&quot;true&quot; data-text-custom-color=&quot;#bf2600&quot;&gt;B&lt;/span&gt; + 비밀키)&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #172b4d;&quot;&gt; 값을 계산한다. 토큰이 변조되지 않았다면 밥이 계산한 해시값은 엘리스가 보낸 것과 동일할 것이고, 변조되었다면 전혀 다른 해시 값을 가진다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;978&quot; data-origin-height=&quot;332&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cLTRd0/btrNB6yMYSY/QyeMvEUthmsEQegpi8Ch41/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cLTRd0/btrNB6yMYSY/QyeMvEUthmsEQegpi8Ch41/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cLTRd0/btrNB6yMYSY/QyeMvEUthmsEQegpi8Ch41/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcLTRd0%2FbtrNB6yMYSY%2FQyeMvEUthmsEQegpi8Ch41%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;978&quot; height=&quot;332&quot; data-origin-width=&quot;978&quot; data-origin-height=&quot;332&quot;/&gt;&lt;/span&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;&quot;&gt;여기서 자연스럽게 한 가지 사실을 알 수 있는데 JWT는 &lt;/span&gt;&lt;b&gt;무결성과 인증을 보장&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #172b4d;&quot;&gt;할 뿐, &lt;/span&gt;&lt;b&gt;기밀성은 보장하지 않는다&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #172b4d;&quot;&gt;. 세부적인 내용은 &lt;/span&gt;&lt;b&gt;HMAC&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #172b4d;&quot;&gt;을 찾아보면 이해가 쉬울 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;&lt;span&gt; &amp;nbsp; JWT 예제 코드&lt;/span&gt;&lt;/h1&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;color: #006dd7;&quot; href=&quot;https://github.com/kangworld/blog_code/tree/main/Java/blogcode/src/main/java/com/kangworld/blogcode/Jwt&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;JWT 코드 Git 저장소&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;MVC 패턴에서 JWT &lt;b&gt;토큰을 발급&lt;/b&gt;하는 간단한 시나리오를 코드로 재현했다. &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&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;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span style=&quot;background-color: #ffffff; color: #172b4d;&quot;&gt;JWT dependency&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1664807086075&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Gradle
implementation group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.11.2'
runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.2'
runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.2'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;Auth 인증 요청과 응답에 사용할 Model 클래스&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;/**
 * 인증 요청에 사용할 클래스
 */
@Getter
@AllArgsConstructor
public class AuthRequest {

    private String id;
    private String password;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;/**
 * 토큰을 감싸는 클래스
 */
@Getter
@AllArgsConstructor
public class AuthResponse {

    private String token;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;AuthTokenService.class&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;public interface AuthTokenService {

    /**
     * Token 생성
     */
    String generateToken(AuthRequest authRequest);

    /**
     * HttpServletRequest 헤더에서 Token 추출
     */
    String extractToken(HttpServletRequest httpServletRequest);

    /**
     * Token 유효성 확인
     */
    void validateToken(String token);

    /**
     * Token에서 만료일 추출
     */
    public DateTime getExpireDate(String token);
}
&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;토큰의 헤더를 설정하고, Claim으로 Issuer(토큰 발급자)와 Expiration(만기일)을 지정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 비밀키를 포함해서 서명을 만들고 토큰을 발급한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;AuthTokenServiceImpl.class&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Service
public class AuthTokenServiceImpl implements AuthTokenService {

    private static final String HEADER_TOKEN_KEY = &quot;TOKEN&quot;;

    private Key key;

    public AuthTokenServiceImpl() {
        String keySource = &quot;jwtKeyString&quot;;
        byte[] keyBytes = Decoders.BASE64.decode(keySource);

        key = Keys.hmacShaKeyFor(keyBytes);
    }

    /**
     * 토큰 발급
     */
    @Override
    public String generateToken(AuthRequest authRequest) {
        Date expireDate = DateTime.now().plusMinutes(30).toDate();

        return Jwts.builder()
                .setHeaderParam(&quot;typ&quot;, &quot;JWT&quot;)
                .setIssuer(&quot;admin&quot;)
                .setExpiration(expireDate)
                .signWith(key, SignatureAlgorithm.HS512).compact();
    }

    ```
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Java/Java</category>
      <author>Kangworld</author>
      <guid isPermaLink="true">https://kangworld.tistory.com/288</guid>
      <comments>https://kangworld.tistory.com/288#entry288comment</comments>
      <pubDate>Tue, 4 Oct 2022 01:58:54 +0900</pubDate>
    </item>
    <item>
      <title>[Java] 쓰레드 로컬 (ThreadLocal)</title>
      <link>https://kangworld.tistory.com/283</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cnZIZj/btrKzOag8ts/ZvN5hFQIVc7nK98Mq6UCJ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cnZIZj/btrKzOag8ts/ZvN5hFQIVc7nK98Mq6UCJ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cnZIZj/btrKzOag8ts/ZvN5hFQIVc7nK98Mq6UCJ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcnZIZj%2FbtrKzOag8ts%2FZvN5hFQIVc7nK98Mq6UCJ0%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;400&quot; height=&quot;250&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&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;h1&gt;&lt;span&gt;✍️&amp;nbsp; Stack, Heap, Thread&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론부터 말하면 ThreadLocal이란 Thread마다 독립적인 변수를 갖도록 지원하는 클래스이다. 쉽게 말하면 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;ThreadLocal을 이해하기 전에 JVM 메모리 정책을 가볍게 짚고 넘어갈 필요가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java 8 이후 기준, &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;메서드 파라미터와 지역 변수는 Stack에 할당&lt;/span&gt;&lt;/b&gt;되고 &lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;그 외의 것들은 Heap에 할당&lt;/span&gt;&lt;/b&gt;된다. 엄밀히 말하면 틀린 설명이지만 메서드에 정의된 것들은 Stack, 그 외의 것들은 Heap 영역이라고 생각하자.&lt;/p&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(이하 쓰레드)란 한 프로세스 내에서 실행되는 흐름의 단위이다. 한 프로세스당 기본적으로 하나의 쓰레드를 갖는데 이를 &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;Stack과 Heap을 이야기하다가 웬 쓰레드를 말하나 싶겠지만, 이 셋은 깊은 연관이 있다.&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;b&gt;쓰레드는&lt;/b&gt; 서로 &lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;Heap 영역은 공유&lt;/span&gt;&lt;/b&gt;하지만 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;Stack 영역은 공유하지 않는 것&lt;/span&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;여담으로, 쓰레드가 공유하는 Heap 영역에 할당된 자원을 &lt;b&gt;공유 자원 &lt;/b&gt;그리고 오직 한 쓰레드만 공유자원에 접근하도록 설계해야 하는 코드 영역을 &lt;b&gt;임계 영역&lt;/b&gt;이라고 한다. 둘 이상의 프로세스가 동시에 임계 영역에 진입하는 것을 방지하기 위해 사용되는 기술이 바로 &lt;b&gt;상호 배제 &lt;/b&gt;기술이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;  ThreadLocal&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본론으로 돌아와서 ThreadLocal이 왜 필요할까.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Stack에 할당된 변수들은 휘발성이 강하다. 메서드가 종료되면 Stack에 저장된 변수들은 사라진다. 반면, 쓰레드 입장에선 메서드에서 계산한 데이터를 잘 보관했다가 재사용하고 싶을 때가 있다. 가령 다른 메서드에서 데이터를 참조해서 사용하거나, 데이터가 &lt;span&gt;Context의 역할을 할 때 그러하다. &lt;/span&gt;그렇다고 해서 메서드가 종료된 이후에도 &lt;span&gt;그 값을&lt;/span&gt; 유지하려고 Heap 영역에 저장했다간 다른&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 class=&quot;cs&quot;&gt;&lt;code&gt;@Data
public class MyRunnable implements Runnable {

    @Override
    public void run() {
        int value = 1;
        value = addOne(value);
        value = multiply(value);

        System.out.println(&quot;Result = &quot; + value);
    }

    private int addOne(int value) {
        return value + 1;
    }

    private int multiply(int value) {
        return value * value;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class App {
    public static void main(String[] args) throws InterruptedException {
        MyRunnable myRunnable = new MyRunnable();
        Thread thread = new Thread(myRunnable);

        thread.start();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1661436428589&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Result = 4&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;그렇다고 해서 MyRunnable에 멤버 변수를 선언하고 여기에 값을 담는 순간 여러 쓰레드에서 변수 접근이 가능하다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Data
public class MyRunnable implements Runnable {

    private int value;

    public MyRunnable() {
        value = 1; // default
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()
                + &quot; Start value : &quot;
                + threadLocal.get());

        addOne();
        multiply();

        System.out.println(Thread.currentThread().getName()
                + &quot; Start value : &quot;
                + threadLocal.get());
    }

    private void addOne() {
        value += 1;
    }

    private void multiply() {
        value *= value;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class App {
    public static void main(String[] args) throws InterruptedException {
        MyRunnable myRunnable = new MyRunnable();
        Thread threadA = new Thread(myRunnable, &quot;Thread A&quot;);
        threadA.start();
        threadA.join();

        Thread threadB = new Thread(myRunnable, &quot;Thread B&quot;);
        threadB.start();
        threadB.join();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Thread B가 myRunnble 코드를 실행할 땐 Thread A가 저장했던 4로 시작한다.&lt;/p&gt;
&lt;pre id=&quot;code_1661436266037&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Thread A Start value : 1
Thread A End value : 4
Thread B Start value : 4
Thread B End value : 25&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;지금 예시로 들었던 두 코드의 문제점을 모두 해결한 것이 ThreadLocal이다. 쉽게 요약하면 ThreadLocal은 쓰레드마다 독립적인 변수를 갖도록 지원한다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class MyThreadLocalRunnable implements Runnable {

    private static ThreadLocal&amp;lt;Integer&amp;gt; threadLocal = ThreadLocal.withInitial(() -&amp;gt; 1);

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()
                + &quot; Start value : &quot; 
                + threadLocal.get());

        addOne();
        multiply();

        System.out.println(Thread.currentThread().getName() 
                + &quot; End value : &quot; 
                + threadLocal.get());
    }

    private void addOne() {
        int value = threadLocal.get();
        threadLocal.set(value + 1);
    }

    private void multiply() {
        int value = threadLocal.get();
        threadLocal.set(value * value);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;public class App {
    
    public static void main(String[] args) throws InterruptedException {
        MyThreadLocalRunnable myThreadLocalRunnable = new MyThreadLocalRunnable();
        Thread threadA = new Thread(myThreadLocalRunnable, &quot;Thread A&quot;);
        threadA.start();
        threadA.join();

        Thread threadB = new Thread(myThreadLocalRunnable, &quot;Thread B&quot;);
        threadB.start();
        threadB.join();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제는 쓰레드마다 독립된 계산 결과가 나온다.&lt;/p&gt;
&lt;pre id=&quot;code_1661439566174&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Thread A Start value : 1
Thread A End value : 4
Thread B Start value : 1
Thread B End value : 4&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;보통 Spring 서버 구현에서 쓰레드마다 User 정보를 Context로 저장할 때 ThreadLocal을 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;&lt;span&gt;❓ ThreadLocal은 항상 좋을까&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;그렇다면 ThreadLocal의 단점 내지는 주의할 점은 무엇일까&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;가령 예제 코드에서 Thread A가 작업을 마무리해서 ThreadLocal에 4를 저장했고, 다시 Thread A로 같은 작업을 시작한다면 이전에 저장했던 4를 시작으로 계산을 진행할 것이다. 이처럼 &lt;b&gt;쓰레드가 한 단위의 작업을 마무리했다면 반드시&lt;/b&gt; &lt;b&gt;ThreadLocal.remove&lt;/b&gt; 메서드를 호출해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;livescript&quot;&gt;&lt;code&gt;public class MyThreadLocalRunnable implements Runnable {

    private static ThreadLocal&amp;lt;Integer&amp;gt; threadLocal = ThreadLocal.withInitial(() -&amp;gt; 1);

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()
                + &quot; Start value : &quot;
                + threadLocal.get());

        addOne();
        multiply();

        System.out.println(Thread.currentThread().getName()
                + &quot; End value : &quot;
                + threadLocal.get());
        
        threadLocal.remove();
    }&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>Java/Java</category>
      <author>Kangworld</author>
      <guid isPermaLink="true">https://kangworld.tistory.com/283</guid>
      <comments>https://kangworld.tistory.com/283#entry283comment</comments>
      <pubDate>Fri, 26 Aug 2022 00:32:06 +0900</pubDate>
    </item>
    <item>
      <title>[Java] 어노테이션(Annotation), @</title>
      <link>https://kangworld.tistory.com/282</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vrkKN/btrKpzqWcUR/TqIwlaCtQahr46EVk99tck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vrkKN/btrKpzqWcUR/TqIwlaCtQahr46EVk99tck/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vrkKN/btrKpzqWcUR/TqIwlaCtQahr46EVk99tck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvrkKN%2FbtrKpzqWcUR%2FTqIwlaCtQahr46EVk99tck%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;400&quot; height=&quot;250&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&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;h1&gt;&lt;span&gt;  Contents&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어노테이션 개념과 &lt;span style=&quot;color: #000000;&quot;&gt;샘플 코드&lt;/span&gt;&lt;/p&gt;
&lt;h1&gt;&lt;span&gt;✍️ Annotation&amp;nbsp;&lt;/span&gt;&lt;/h1&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-renderer-start-pos=&quot;106&quot; data-ke-size=&quot;size16&quot;&gt;먼저 어노테이션은 &lt;b&gt;1. 문서화&lt;/b&gt; &lt;b&gt;2. 컴파일러 체크&lt;/b&gt; &lt;b&gt;3. 메타데이터&lt;/b&gt; 용도로 사용된다. 문법적으로 &lt;b&gt;@&lt;/b&gt;기호가 붙은 심볼을 사용하며 패키지, 클래스, 메서드, 프로퍼티, 변수에 명시할 수 있다. 어노테이션이 붙은 코드를 컴파일 시에 수집해서 API 문서화에 사용되기도 하지만 JavaDoc이라는 좋은 문서화 도구가 있기에 &lt;b&gt;문서화&lt;/b&gt;는 가장 비중이 낮은 어노테이션 사용법이다. 이외에도 &lt;b&gt;컴파일 타임에 에러나 경고&lt;/b&gt;를 발생시켜 개발자에게 &lt;b&gt;위험 요소를 알리는 목적&lt;/b&gt;으로도 사용된다. (= @Override)&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;106&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;364&quot; data-ke-size=&quot;size16&quot;&gt;가장 큰 비중을 갖는 것은 &lt;b&gt;메타데이터로&lt;/b&gt;서의 용도이다. 메타데이터란 데이터를 위한 데이터, 데이터를 설명하는 데이터를 의미한다. 메타데이터로서 부가적인 표현뿐만 아니라 리플렉션을 접목하면 특정 클래스의 객체를 생성하고 주입하는 것이 가능하다. (= @Autowired)&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;364&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;  Built-in Annotation&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바 SDK에서 기본적으로 제공하는 빌트인 어노테이션들이 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;575&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;@Override&lt;/b&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;575&quot; data-ke-size=&quot;size16&quot;&gt;현재 메서드가 슈퍼 클래스 혹은 인터페이스의 메서드를 오버라이드 했음을 컴파일러에게 명시한다. 만약 형식에 맞지 않게 메서드를 구현했다면 컴파일러가 인지하고 에어를 발생한다.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;575&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;685&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;@Deprecated &lt;/b&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;685&quot; data-ke-size=&quot;size16&quot;&gt;마커 어노테이션으로 메서드를 사용하지 않도록 유도한다. 만약 사용한다면 컴파일 경고를 일으킨다.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;685&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;753&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;@FunctionalInterface &lt;/b&gt;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;753&quot; data-ke-size=&quot;size16&quot;&gt;해당 인터페이스가 함수형 인터페이스임을 명시한다. 만약 추상 메서드가 없거나 두 개이상 있다면 컴파일 에러가 발생한다.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;753&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;&lt;span&gt;⚾ Meta Annotation&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메타 어노테이션이란 다른 어노테이션에서 사용되는 어노테이션을 말하며 후에 서술할 커스텀 어노테이션을 생성할 때 주로 사용된다. 가령 @Service 어노테이션은 @Component 어노테이션을 내포하고 있는데, 여기서 @Component가 메타 어노테이션에 해당한다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {

   @AliasFor(annotation = Component.class)
   String value() default &quot;&quot;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface Component {

   String value() default &quot;&quot;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #172b4d;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이외에도 대표적인 메타 어노테이션으로&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;1370&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;@Retention &lt;/b&gt;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;1370&quot; data-ke-size=&quot;size16&quot;&gt;어노테이션이 적용되고 유지되는 범위를 명시하기 위해 사용한다.&lt;/p&gt;
&lt;pre id=&quot;code_1661265074207&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Retention(RetentionPolicy.RUNTIME) // 컴파일 이후에도 JVM에 의해서 참조가 가능하다.
@Retention(RetentionPolicy.CLASS)   // 컴파일러가 클래스를 참조할 때까지 유효하다.
@Retention(RetentionPolicy.SOURCE)  // 어노테이션 정보는 컴파일 이후 없어진다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;@Target&lt;/b&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어노테이션을&amp;nbsp;적용할&amp;nbsp;위치를&amp;nbsp;결정한다.&lt;span style=&quot;background-color: #ffffff; color: #172b4d;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1661265122060&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Target({ ElementType.PACKAGE, 			// 패키지 선언
		ElementType.TYPE, 				// 타입 선언
		ElementType.CONSTRUCTOR, 		// 생성자 선언
		ElementType.FIELD, 				// 멤버 변수 선언
		ElementType.METHOD, 			// 메소드 선언
		ElementType.ANNOTATION_TYPE, 	// 어노테이션 타입 선언
		ElementType.LOCAL_VARIABLE, 	// 지역 변수 선언
		ElementType.PARAMETER, 			// 매개 변수 선언
		ElementType.TYPE_PARAMETER,		// 매개 변수 타입 선언
		ElementType.TYPE_USE 			// 타입 사용
})&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;@Inherited&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자식 클래스가 어노테이션을 상속받을 수 있다.&amp;nbsp; &lt;br /&gt;&lt;br /&gt;&lt;b&gt;@Repeatable&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반복적으로&amp;nbsp;어노테이션을&amp;nbsp;선언할&amp;nbsp;수&amp;nbsp;있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;&lt;span&gt;✨ &lt;b&gt;Custom Annotation&lt;/b&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;자바에서 커스텀 어노테이션을 선언하는 것은 간단하다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1661265226825&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public @interface SimpleAnnotation {

}&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-renderer-start-pos=&quot;2315&quot; data-ke-size=&quot;size16&quot;&gt;가령, 멤버 변수에 명시 가능하고&lt;b&gt; @Target(ElementType.FIELD)&lt;/b&gt;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;2360&quot; data-ke-size=&quot;size16&quot;&gt;컴파일 이후에도 JVM에 의해 참조가 가능한 어노테이션 &lt;b&gt;@Retention(RetentionPolicy.RUNTIME)&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1661265247357&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SimpleAnnotation {

	/* enum 타입을 선언 */
	public enum Status {
		STOP, MOVE
	}

	/* enum 사용 */
	Status quality() default Status.STOP;

	/* String 사용 */
	String value();

	/* 배열 사용 */
	int[] values();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 어노테이션도 마치 클래스처럼 일종의 멤버 변수인 &lt;b&gt;Element&lt;/b&gt;를 갖는 것이 가능하다.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;2762&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;a style=&quot;color: #006dd7;&quot; href=&quot;https://docs.oracle.com/javase/specs/jls/se8/html/jls-9.html#jls-9.6.1&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-renderer-mark=&quot;true&quot;&gt;공식 문서&lt;/a&gt;&lt;/span&gt;에 따르면 아래와 같은 타입만 허용한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;A primitive type&lt;/li&gt;
&lt;li&gt;String&lt;/li&gt;
&lt;li&gt;Class or an invocation of Class&lt;/li&gt;
&lt;li&gt;An enum type&lt;/li&gt;
&lt;li&gt;An annotation type&lt;/li&gt;
&lt;li&gt;An array type whose component type is one of the preceding types&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-renderer-start-pos=&quot;2964&quot; data-ke-size=&quot;size16&quot;&gt;후에 예시로 등장하겠지만 &lt;b&gt;어노테이션 자체는 메타데이터를 담는 표식&lt;/b&gt;에 불과하지만 &lt;b&gt;리플렉션을 통한 어노테이션의 적용 여부, 엘리먼트 값 처리 등&lt;/b&gt;을 할 수 있기에 매우 유용 사용된다.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;2964&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;&lt;span&gt; ️ Annotation &amp;amp; Reflection Sample Code&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;a style=&quot;color: #006dd7;&quot; href=&quot;https://github.com/kangworld/blog_code/tree/main/Java/blogcode&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;샘플 코드 깃 주소&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;@StringInjector 어노테이션 &lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;crystal&quot;&gt;&lt;code&gt;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface StringInjector {

    String value() default &quot;Default name&quot;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;@StringInjector를 적용한 MyObject 클래스&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Data
public class MyObject {

    @StringInjector(&quot;Bart&quot;)
    private String name;

    @StringInjector
    private String defaultName;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;Annotation &amp;amp; Reflection 처리 로직&lt;/p&gt;
&lt;pre class=&quot;smali&quot;&gt;&lt;code&gt;import java.lang.reflect.Field;

public class MyContextContainer {
    public &amp;lt;T&amp;gt; T get(Class&amp;lt;T&amp;gt; clazz) throws IllegalAccessException, InstantiationException {
        T instance = clazz.newInstance();
        instance = invokeAnnonations(instance);
        return instance;
    }

    private &amp;lt;T&amp;gt; T invokeAnnonations(T instance) throws IllegalAccessException {
        // 클래스 필드를 가져오고
        Field[] fields = instance.getClass().getDeclaredFields();
        for (Field field : fields) {
            // 필드에 붙은 어노테이션을 가져오고
            StringInjector annotation = field.getAnnotation(StringInjector.class);

            if (annotation != null &amp;amp;&amp;amp; field.getType() == String.class) {
                // 어노테이션의 엘리먼트 값으로 필드 값을 설정
                field.setAccessible(true);
                field.set(instance, annotation.value());
            }
        }
        return instance;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;테스트 코드&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;class MyContextContainerTest {

    @Test
    public void test_ANNOTATION_REFLECTION() throws Exception {
        MyContextContainer myContextContainer = new MyContextContainer();
        MyObject myObject = myContextContainer.get(MyObject.class);

        assertThat(myObject.getName()).isEqualTo(&quot;Bart&quot;);
        assertThat(myObject.getDefaultName()).isEqualTo(&quot;Default name&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Java/Java</category>
      <author>Kangworld</author>
      <guid isPermaLink="true">https://kangworld.tistory.com/282</guid>
      <comments>https://kangworld.tistory.com/282#entry282comment</comments>
      <pubDate>Tue, 23 Aug 2022 23:42:09 +0900</pubDate>
    </item>
    <item>
      <title>[2022.08.16 일상] 돌숭이, 일기 쓰기</title>
      <link>https://kangworld.tistory.com/274</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;돌숭이&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;슬랙 상태 이모지를 뭐로 할까.. 고민하다가&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이말년 서유기의 주인공 돌숭이로 결정했다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;근데 뭐랄까... 돌숭이가 아니라...&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;흑백 세상의 아보카도 같아서 심심했는데&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;iOS 이미지.jpg&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;480&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/peVbb/btrJDhYxphd/5Rd1zACCAwFYODhxkQVSt1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/peVbb/btrJDhYxphd/5Rd1zACCAwFYODhxkQVSt1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/peVbb/btrJDhYxphd/5Rd1zACCAwFYODhxkQVSt1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpeVbb%2FbtrJDhYxphd%2F5Rd1zACCAwFYODhxkQVSt1%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;480&quot; height=&quot;480&quot; data-filename=&quot;iOS 이미지.jpg&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;480&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;친절한 회사 디자이너분이 채색해 주셨다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;갸꿀&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;돌숭이.png&quot; data-origin-width=&quot;360&quot; data-origin-height=&quot;360&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pdfGF/btrJMzxpQ7g/HmujNWBcxLBSDxaFBU8XOk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pdfGF/btrJMzxpQ7g/HmujNWBcxLBSDxaFBU8XOk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pdfGF/btrJMzxpQ7g/HmujNWBcxLBSDxaFBU8XOk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpdfGF%2FbtrJMzxpQ7g%2FHmujNWBcxLBSDxaFBU8XOk%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;360&quot; height=&quot;360&quot; data-filename=&quot;돌숭이.png&quot; data-origin-width=&quot;360&quot; data-origin-height=&quot;360&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;진짜 너무 귀엽다...&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;217&quot; data-origin-height=&quot;90&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mphDq/btrJNIgyONt/CocK8oUAP8kLt8k17oZRSK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mphDq/btrJNIgyONt/CocK8oUAP8kLt8k17oZRSK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mphDq/btrJNIgyONt/CocK8oUAP8kLt8k17oZRSK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmphDq%2FbtrJNIgyONt%2FCocK8oUAP8kLt8k17oZRSK%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;217&quot; height=&quot;90&quot; data-origin-width=&quot;217&quot; data-origin-height=&quot;90&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 style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;일기 쓰기&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이전까지는 블로그에 하나의 글을 쓰기까지 매우 오랜 시간이 걸렸다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;어떻게 하면 읽기 편하게 쓸지&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;글의 구성은 어떻게 할지&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;정말 필요한 글인지&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;고민하다 미룬 글들이 오히려 더 많다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그러다 보니 써야 할 글은 쌓여있고&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;글쓰기 = 무거운 일이 되어버려 손이 잘 안 갔다...&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 요즘 생각을 바꾼 게&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;글이 짧고, 큰 의미가 없고, 사소하더라도&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;하루에 겪었던 일상과 감정을 기록하면 좋을 것 같다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;매일은 아니더라도~&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그나저나 오늘 건강검진인데 잠은 안 오고&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;가는 길에 침착맨 만났으면 좋겠다.&lt;/p&gt;</description>
      <category>일상</category>
      <author>Kangworld</author>
      <guid isPermaLink="true">https://kangworld.tistory.com/274</guid>
      <comments>https://kangworld.tistory.com/274#entry274comment</comments>
      <pubDate>Tue, 16 Aug 2022 00:48:02 +0900</pubDate>
    </item>
    <item>
      <title>[Java] Chapter 1-5. Java의 Class 실습</title>
      <link>https://kangworld.tistory.com/203</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rpy7q/btrrJHsUxgY/fzoZehbOAF1J88PnDsUGAK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rpy7q/btrrJHsUxgY/fzoZehbOAF1J88PnDsUGAK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rpy7q/btrrJHsUxgY/fzoZehbOAF1J88PnDsUGAK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Frpy7q%2FbtrrJHsUxgY%2FfzoZehbOAF1J88PnDsUGAK%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;400&quot; height=&quot;250&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&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;h1&gt;&lt;span&gt;✍️ Class 실습&lt;/span&gt;&lt;/h1&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Book 클래스(설계도)&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1643117644002&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Book {
	public String title; //String : 문자열을 저장하는 자료형
	public int price;
	public String publisher;
	public String author;
	public int page;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1643113407943&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Book book;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;변수&lt;/b&gt; book은 클래스의 객체이다. 서술한 것처럼 객체도 하나의 &lt;b&gt;변수&lt;/b&gt;로 한 개의 데이터만 저장할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;book 자체는 한 개의 데이터만 저장할 수 있음에도 불구하고 book은 title, price, publisher, author, page에 대한 데이터를 모두 포함해야 한다. 이게 어떻게 가능할까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 title, price, publisher, author, page에 대한 정보는 메모리상 어딘가에 저장되어 있고 book은 그 메모리 주소 담고 있는 일종의 레퍼런스이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;  new 키워드&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;title, price, publisher, author, page에 대한 정보는 메모리상 어딘가에 있고 book은 그곳을 가리킨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 조금 더 의문을 가져보자.&lt;/p&gt;
&lt;pre id=&quot;code_1643118460453&quot; class=&quot;abnf&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Book book;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 변수 book을 선언만 해도 title, price, publisher, author, page을 위한 메모리 공간이 할당될까? &lt;b&gt;그렇지 않다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스의 객체를 선언만 하면 실제 속성과 메서드가 할당될 메모리 공간을 가리킬 &lt;b&gt;준비&lt;/b&gt;만 된 상태이고 실제 book을 이루는 데이터들의 메모리 공간이 할당되진 않는다.&amp;nbsp;&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;코드를 작성해 보면 book의 price에 10을 할당하려 하면 에러가 발생한다.&lt;/p&gt;
&lt;pre id=&quot;code_1643118761808&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static void main(String[] args) {
	Book book;
	book.price = 10; // error!
}&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: #f6e199;&quot;&gt;new&lt;/span&gt; 키워드로, 객체에 실제 메모리를 할당할 때 new를 사용한다.&lt;/p&gt;
&lt;pre id=&quot;code_1643118959513&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static void main(String[] args) {
	Book book;
	book = new Book();
	
	book.price = 10; // OK!!
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;new 키워드로 실제 메모리 공간을 할당하면 그 순간부터 book을 Book 클래스의 '&lt;span style=&quot;background-color: #f6e199;&quot;&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;코드상의 변화를 알아보면, new 키워드로 book이 가리킬 메모리 공간을 할당하면 book에 &lt;b&gt;.&lt;/b&gt;을 붙여 속성값을 가져오거나 새로운 값을 할당할 수 있는 멋진 일이 발생한다.&lt;/p&gt;
&lt;pre id=&quot;code_1643120405640&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static void main(String[] args) {
	Book book;
	book = new Book();
	
	book.title = &quot;demian&quot;;
	book.price = 2000;
	book.publisher = &quot;hello&quot;;
	book.author = &quot;word&quot;;
	book.page = 123;
	
	System.out.println(book.title);
	System.out.println(book.price);
	System.out.println(book.publisher);
	System.out.println(book.author);
	System.out.println(book.page);
}

// 출력
demian
2000
hello
word
123&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot; -변수의-선언과-할당,-심볼테이블&quot;&gt;&lt;span&gt;  실습, Person 클래스 만들기&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Person&lt;/b&gt; 클래스를 만들고 new 키워드를 사용해서 실제 객체에 메모리를 할당하고 원하는 값을 넣고 출력한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조건 1) Person 클래스의 속성은 &lt;b&gt;이름(name), 나이(age), 키(height), 몸무게(weight)&lt;/b&gt;로 정의한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조건 2) 키와 몸무게 같은 실수형 데이터는 기본 자료형인 dobule로 선언한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1643120861619&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static void main(String[] args) {
	Person person = new Person();
	
	person.name = &quot;침착맨&quot;;
	person.age = 20;
	person.height = 179.9;
	person.weight = 70.4;
	
	System.out.println(person.name);
	System.out.println(person.age);
	System.out.println(person.height);
	System.out.println(person.weight);
}

// 출력
침착맨
20
179.9
70.4&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;</description>
      <category>Java/Java</category>
      <author>Kangworld</author>
      <guid isPermaLink="true">https://kangworld.tistory.com/203</guid>
      <comments>https://kangworld.tistory.com/203#entry203comment</comments>
      <pubDate>Tue, 25 Jan 2022 23:30:25 +0900</pubDate>
    </item>
    <item>
      <title>[Java] Chapter 1-4. Java의 Class, 사용자 정의 자료형</title>
      <link>https://kangworld.tistory.com/202</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/H8OBm/btrruohBeK2/hr2yUP9kpngdYItkI4INOk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/H8OBm/btrruohBeK2/hr2yUP9kpngdYItkI4INOk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/H8OBm/btrruohBeK2/hr2yUP9kpngdYItkI4INOk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FH8OBm%2FbtrruohBeK2%2Fhr2yUP9kpngdYItkI4INOk%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;400&quot; height=&quot;250&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&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;h1&gt;&lt;span&gt;✍️ 기본 자료형 VS 사용자 정의 자료형&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기 문제가 하나 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;정수 한 개를 저장하기 위한 변수 a를 선언하세요.&quot;&lt;/p&gt;
&lt;pre id=&quot;code_1643029314234&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int a;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;정수형 변수 a를 선언했으면 a가 가진 메모리 공간에 10을 할당하세요.&quot;&lt;/p&gt;
&lt;pre id=&quot;code_1643029354890&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;a = 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;&lt;span&gt;또 문제가 주어졌다. &lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&quot;책 한 권을 저장하기 위한 변수를 선언하세요.&quot;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1643029594311&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Book book; // ???&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;Book이라는 자료형이 담고 있을 정보는 무수히 많다.&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;여기서 어떤 사물을 설계하기 위한 도구가 바로 class이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;class라는 개념을 이용해 Book을 설계해 보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Book이라는 이름을 가진 Class 파일을 하나 추가하자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;618&quot; data-origin-height=&quot;247&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDXwDw/btrrB5ONMvE/6XRLGz9calxigZjWZyUqp1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDXwDw/btrrB5ONMvE/6XRLGz9calxigZjWZyUqp1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDXwDw/btrrB5ONMvE/6XRLGz9calxigZjWZyUqp1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDXwDw%2FbtrrB5ONMvE%2F6XRLGz9calxigZjWZyUqp1%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;618&quot; height=&quot;247&quot; data-origin-width=&quot;618&quot; data-origin-height=&quot;247&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;앞으로 책과 같은 현실 세계의 사물을 '&lt;span style=&quot;background-color: #f6e199;&quot;&gt;객체&lt;/span&gt;'라고 부를 것이고 책이 가지는 상태 정보를 '&lt;span style=&quot;background-color: #f6e199;&quot;&gt;속성&lt;/span&gt;'이라 부를것이다. 나아가 객체에는 행위가 포함되는데 이를 '&lt;span style=&quot;background-color: #f6e199;&quot;&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;Book을 class의 형태로 설계하면 아래와 같고 이제부터 Book을 사용자 정의 자료형이라고 부를 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 String은 문자열을 저장하는 자료형이다.&lt;/p&gt;
&lt;pre id=&quot;code_1643030892572&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Book {
	public String title; //String : 문자열을 저장하는 자료형
	public int price;
	public String publisher;
	public String author;
	public int page;
}&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;설계가 끝났으니 Book의 변수를 만들어보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그전에 정수형 변수를 살펴보고 넘어가자. 정수형 변수 a는 선언 시 정수를 위한 메모리 공간이 할당된다. 그렇기에 변수 a에 10을 바로 넣을 수 있는 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1643031351023&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int a;
a = 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;반면 Book은 여러 변수들의 집합으로 이루어진 자료형이다. 본디 하나의 변수엔 하나의 값만 저장할 수 있는데 어떻게 변수 book에 title이며 price며 여러 값들을 저장할 수 있을까?&lt;/p&gt;
&lt;pre id=&quot;code_1643031677269&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Book book; //title, price, publisher... 여러 값들을 어떻게 book 하나에 넣지?&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;후에 다루겠지만 메모리 공간 어딘가에 변수 book이 가지는 속성(제목, 가격, 출판사)을 저장하고 있고, 변수 book은 그 메모리 공간을 참조하는 구조이기에 book은 자신이 가진 속성과 메서드에 접근할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 100번지에 속성값들이 저장되어 있으면 변수 book은 100번지를 가리키는 형태이다.&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;몇 가지 더 설명하면, 다음과 같이 Book book; 변수 book을 선언하면 book을 '&lt;span style=&quot;background-color: #f6e199;&quot;&gt;객체&lt;/span&gt;'라고 부르며 book에 실제 메모리 공간을 할당하면 book은 Book 클래스의 '&lt;span style=&quot;background-color: #f6e199;&quot;&gt;인스턴스&lt;/span&gt;'라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;  정리&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;세상에 존재하는 모든 사물은 객체로 만들 수 있으므로 class를 잘 설계하는 것이 객체지향 프로그래밍의 시작점이다.&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Java/Java</category>
      <author>Kangworld</author>
      <guid isPermaLink="true">https://kangworld.tistory.com/202</guid>
      <comments>https://kangworld.tistory.com/202#entry202comment</comments>
      <pubDate>Mon, 24 Jan 2022 23:21:43 +0900</pubDate>
    </item>
    <item>
      <title>[Java] Chapter 1-3. 프로그래밍의 3대 요소(변수, 자료형, 할당)</title>
      <link>https://kangworld.tistory.com/201</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/t6pfl/btrroyScSjz/klgFWv2nHVzKTOXO4mmRGK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/t6pfl/btrroyScSjz/klgFWv2nHVzKTOXO4mmRGK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/t6pfl/btrroyScSjz/klgFWv2nHVzKTOXO4mmRGK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ft6pfl%2FbtrroyScSjz%2FklgFWv2nHVzKTOXO4mmRGK%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;400&quot; height=&quot;250&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&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;h1&gt;&lt;span&gt;✍️ 프로그래밍의 3대 요소&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그래밍의 3대 요소를 이해하면 Java뿐만 아니라 어떤 언어를 배워도 쉽게 이해할 수 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;프로그래밍의 3대 요소 : &lt;span style=&quot;background-color: #99cefa;&quot;&gt;변수&lt;/span&gt;, &lt;span style=&quot;background-color: #99cefa;&quot;&gt;자료형(Data type)&lt;/span&gt;, &lt;span style=&quot;background-color: #99cefa;&quot;&gt;할당(=)&lt;/span&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;머릿속으로 493+1982를 구해보자. 피연산자 493과 1982를 머릿속으로 기억하고 계산해서 덧셈의 결과인 2475를 머릿속 어딘가에 잘 저장할 것이다.&lt;/p&gt;
&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;프로그램도 마찬가지다. 493과 1982를 어딘가에 저장하고 더하고 결괏값을 저장해둘 것이다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;여기서 말하는 어딘가가 바로 메인 메모리를 의미하고 더 구체적으로 493, 1982, 2475가 저장되는 공간을 &lt;span style=&quot;background-color: #99cefa;&quot;&gt;변수&lt;/span&gt;라고 한다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;변수는 다른 말로 기억 공간이라고 해석할 수 있으며 변수를 만들기 위해선 두 가지 요소를 고려해야 한다. &lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;첫 번째로 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;'크기'&lt;/span&gt;를 고려해야 하고 두 번째로 정수인지 실수인지 문자인지 등 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;'&lt;span style=&quot;background-color: #f6e199;&quot;&gt;어&lt;/span&gt;떤 종류의 데이터를 저장할 것인가'&lt;/span&gt;도 고려해야 한다. &lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;프로그래밍 언어는 변수의 크기와 데이터의 종류를 함께 고려해서 이미 정의된 키워드를 개발자에게 제공하는데 이것이 바로 &lt;span style=&quot;background-color: #99cefa;&quot;&gt;자료형&lt;/span&gt;이다. Java에서 4byte 정수형 변수의 자료형은 int, 8byte 실수형 변수의 자료형은 double이 대표적인 자료형이다.&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;자바 프로그래밍을 예시로 4byte 정수형 변수를 만들되 그 이름을 a라고 하고 싶다면 다음과 같이 작성할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 이것을 조금 더 전문적인 용어로 'a라는 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;변수를 선언했다.&lt;/span&gt;'라고 말한다.&lt;/p&gt;
&lt;pre id=&quot;code_1642946791928&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int a;&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: #99cefa;&quot;&gt;할당&lt;/span&gt;이라고 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1642947184352&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int a, b, c;
a = 493;
b = 1982;
c = 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;span style=&quot;background-color: #99cefa;&quot;&gt;변수&lt;/span&gt;, 변수의 크기와 종류를 결정짓는 것은 &lt;span style=&quot;background-color: #99cefa;&quot;&gt;자료형&lt;/span&gt;, 변수에 데이터를 집어넣는 &lt;span style=&quot;background-color: #99cefa;&quot;&gt;할당&lt;/span&gt;만 이해하면 프로그래밍을 시작할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1642947241703&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int a, b, c;
a = 493;
b = 1982;
c = a + b;

System.out.println(&quot;덧셈의 결과 &quot; + c);
// 덧셈의 결과 2475&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;  Java의 자료형&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴파일러에서 기본적으로 제공해 주는 자료형을 기본 자료형이라 부른다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정수형 : short, int, long&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실수형 : float, double&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문자형 : char&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;논리형 : boolean&lt;/p&gt;
&lt;pre id=&quot;code_1642948265980&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int i = 10; // 정수형 4byte
System.out.println(i);

float f = 12.34f; // 실수형 4byte
System.out.println(f);

char c = 'A'; // 문자형 2byte
System.out.println(c);

boolean b = true;
System.out.println(b);

결과
10
12.34
A
true&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;간단한 프로그램은 기본 자료형만으로도 충분히 작성할 수 있지만 복잡한 프로그램은 기본 자료형만으론 해결할 수 없다. 사람, 책, 자동차 등 여러 정보를 포함하는 사물에 대한 기본 자료형은 제공하지 않기에 개발자가 직접 만들어서 사용해야 하는데 이를 사용자 정의 자료형이라 부르며 Java에선 클래스를 도입해서 이러한 문제를 해결하고 있다. 나아가 클래스를 활용해 프로그래밍하는 것을 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;객체지향 프로그래밍&lt;/span&gt;이라 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1642948383265&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Person person;
//기본적으로 이런 자료형은 없다.
//Person이 가진 성질을 이해하고 내가 설계해야함
//Person은 이름, 생년월일, 주민등록 번호, 성별이 포함될 수 있다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;&lt;span&gt;  변수의 선언과 할당, 심볼테이블&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변수를 선언하면 변수를 위한 공간이 메인 메모리에 할당되며 공간의 위치를 '주소'라는 개념으로 구분한다.&lt;/p&gt;
&lt;pre id=&quot;code_1642948766301&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int a;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 변수 a를 위한 공간이 메인 메모리의 주소 1000번지라고 가정하면 값 999는 1000번지에 저장된 셈이다.&lt;/p&gt;
&lt;pre id=&quot;code_1642949046662&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;a = 999;&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 style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;변수 a를 선언한 시점에서 심볼테이블의 상태 [ a , 1000 ]&lt;/p&gt;
&lt;pre id=&quot;code_1642949488470&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int a; // a의 주소는 1000&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같이 코드를 작성하면 심볼테이블에서 a를 찾고 a의 주소인 1000을 참조해 메인 메모리에 888을 할당하게 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1642950072294&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;a = 888;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 변수 b에 666을 할당하려 한다면 심볼테이블에서 b를 찾을 것이고, 테이블상에서 b라는 변수는 없기에 에러가 발생할 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1642950129579&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;b = 666; // error!&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;</description>
      <category>Java/Java</category>
      <author>Kangworld</author>
      <guid isPermaLink="true">https://kangworld.tistory.com/201</guid>
      <comments>https://kangworld.tistory.com/201#entry201comment</comments>
      <pubDate>Mon, 24 Jan 2022 00:04:44 +0900</pubDate>
    </item>
    <item>
      <title>[Java] Chapter 1-2. 자바의 구동방식(JVM)</title>
      <link>https://kangworld.tistory.com/200</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/A55Da/btrrxYVyTWg/cDERXfxvg3vJqT4lKZsn5k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/A55Da/btrrxYVyTWg/cDERXfxvg3vJqT4lKZsn5k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/A55Da/btrrxYVyTWg/cDERXfxvg3vJqT4lKZsn5k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FA55Da%2FbtrrxYVyTWg%2FcDERXfxvg3vJqT4lKZsn5k%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;400&quot; height=&quot;250&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&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;h1 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;✍️ 자바의 구동방식&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바 소스 코드(.java 파일)를 자바 컴파일러(javac)로 컴파일하면 결과물로 .class 확장자를 가진 파일이 생성된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;.class 파일은 바로 실행할 수 없는 JVM만이 이해할 수 있는 bytecode로 구성된 파일로 자바 소스 코드를 실행하기 위해선 JVM을 반드시 거쳐서 실행해야 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;519&quot; data-origin-height=&quot;225&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c0A4WU/btrrdbnCkKA/Y3nJD6xpXtqQpAutbsdRd0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c0A4WU/btrrdbnCkKA/Y3nJD6xpXtqQpAutbsdRd0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c0A4WU/btrrdbnCkKA/Y3nJD6xpXtqQpAutbsdRd0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc0A4WU%2FbtrrdbnCkKA%2FY3nJD6xpXtqQpAutbsdRd0%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;519&quot; height=&quot;225&quot; data-origin-width=&quot;519&quot; data-origin-height=&quot;225&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;JVM을 사용하는 이유&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바 소스 코드를 실행하는 과정은 어느 정도 알겠는데 굳이 JVM을 사용하는 이유는 뭘까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 이해하기 위해선 Java 이전의 프로그래밍언어인 C를 살펴볼 필요가 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C/C++ 등 자바 이전의 프로그래밍 언어로 작성된 프로그램은 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;컴파일 시&lt;/span&gt; 재배치 가능한 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;오브젝트 파일&lt;/span&gt;이 생성되는데 이는 컴퓨터가 이해할 수 있는 기계어로 구성되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 오브젝트 파일을 만들어내는 컴파일러와 어셈블러는 현재 탑재된 CPU와 OS 구조를 참조해서 오브젝트 파일을 생성하기 때문에 다른 CPU와 OS가 탑재된 시스템에선 해당 오브젝트 파일을 해석할 수 없다. 예를 들어, Intel CPU와 Windows가 탑재된 컴퓨터에서 컴파일된 오브젝트 파일은 Intel CPU와 Linux가 탑재된 컴퓨터에서 해석할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;C 계열 언어의 특징을 '플랫폼에 종속적이다.'&lt;/span&gt;라고 한다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;822&quot; data-origin-height=&quot;278&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XXoct/btrrjhVyixd/KNGhfwgBtqIrghjOLuDZkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XXoct/btrrjhVyixd/KNGhfwgBtqIrghjOLuDZkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XXoct/btrrjhVyixd/KNGhfwgBtqIrghjOLuDZkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXXoct%2FbtrrjhVyixd%2FKNGhfwgBtqIrghjOLuDZkK%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;822&quot; height=&quot;278&quot; data-origin-width=&quot;822&quot; data-origin-height=&quot;278&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;컴파일러와 어셈블러는 탑재된 CPU 구조를 참조한다?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그램(실행 가능한 파일)이란 0과 1로 구성된 명령어들의 집합으로 사람에 빗어 표현하면 '공부해, 청소해, 밥 먹어&quot; 등의 컴퓨터가 수행할 동작들을 기계어로 작성해놨다고 생각하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 말한 프로그램을 실행하면 명령어들이 메인 메모리상에 적재되고 프로그램은 실행상태에 놓이게 된다. 이렇게 메모리에 적재되어 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;실행 중인 프로그램을 프로세스&lt;/span&gt;라고 부른다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 CPU는 메인 메모리상에 적재된 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;명령어&lt;/span&gt;를 읽어오고 이에 따른 연산을 수행함으로써 프로그램이 동작하게 되는데 연산을 수행하는 주체인 CPU의 제조사마다 명령어가 다르다는 문제점을 가지고 있다. 마치 '밭 갈아!'라는 명령어를 한국인이라면 잘 이해하지만 외국인은 이해하지 못하는 것처럼 말이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 컴파일러는 특정 CPU를 타깃으로 한 어셈블리 프로그램을 만들고 어셈블러는 컴파일러의 결과물을 재배치 가능한 오브젝트 파일로 변환한다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 이유로 컴파일러와 어셈블러는 CPU 구조를 참조할 수밖에 없는 종속인 형태를 띠게 된다.&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;다시 JVM&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 자바로 돌아와서 이야기하면, 자바 컴파일러는 탑재된 CPU가 Intel CPU든 AMD CPU든 결과물로 동일한 .class 파일을 생성하면 되고 .class 파일을 현재 환경에 맞게 해석하는 작업은 오롯이 JVM이 수행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하면 자바 컴파일러는 OS와 CPU에 구애받지 않고 .class 파일을 만드는 것에 집중하고 그것을 환경에 맞게 해석하는 것은 JVM이 담당한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 구조의 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;장점&lt;/span&gt;으로&amp;nbsp;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;환경이 변하더라도 코드를 다시 컴파일하거나 수정할 필요 없이 .class 파일과 현재 환경에 맞는 JVM만 있으면 어디서든 실행 가능한 파일을 만들 수 있다.&lt;/span&gt; 이러한 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;자바의 특징을 '플랫폼에 독립적이다.'&lt;/span&gt;라고 한다. &amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 자바도 완벽하게 플랫폼에 독립적이진 않다. C언어의 컴파일러가 CPU에 종속적인 것처럼 자바의 JVM도 CPU와 OS에 종속적일 수밖에 없다. 결국 자바도 현재 실행 환경에 맞는 JVM을 따로 설치해야한다는 불편한 진실이 숨어있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;  커맨드 라인으로 자바 실행하기&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;1.간단하게 hello world!를 출력하는 코드를 작성하고&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1642931209715&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class JavaTest {
	public static void main(String[] args) {
		System.out.println(&quot;hello world!&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;2. CMD창을 열어 소스코드가 있는 src 폴더로 이동&lt;/p&gt;
&lt;pre id=&quot;code_1642932574981&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;C:\Users\USERNAME&amp;gt;

cd C:\Users\USERNAME\Java\workspace\JavaHello\src

C:\Users\USERNAME\Java\workspace\JavaHello\src&amp;gt;&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;3. 자바 컴파일러 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;javac&lt;/span&gt;로 소스코드를 컴파일하기&lt;/p&gt;
&lt;pre id=&quot;code_1642932668550&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;C:\Users\USERNAME\Java\workspace\JavaHello\src&amp;gt;javac.exe JavaTest.java&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;4. 컴파일의 결과물로 .class 파일이 생생 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;281&quot; data-origin-height=&quot;83&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c1wJG6/btrrxXPQSOB/jTGTl5N0IoacbKZWqL9m10/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c1wJG6/btrrxXPQSOB/jTGTl5N0IoacbKZWqL9m10/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c1wJG6/btrrxXPQSOB/jTGTl5N0IoacbKZWqL9m10/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc1wJG6%2FbtrrxXPQSOB%2FjTGTl5N0IoacbKZWqL9m10%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;281&quot; height=&quot;83&quot; data-origin-width=&quot;281&quot; data-origin-height=&quot;83&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;5. .class 파일을 확장자명 없이 java.exe 명령어에 넘겨주면 자바 소스코드가 실행된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;java.exe는 내부적으로 jvm을 구동하는 명령어라고 생각하면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1642934351930&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;C:\Users\USERNAME\Java\workspace\JavaHello\src&amp;gt;java.exe JavaTest
hello world!&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Java/Java</category>
      <author>Kangworld</author>
      <guid isPermaLink="true">https://kangworld.tistory.com/200</guid>
      <comments>https://kangworld.tistory.com/200#entry200comment</comments>
      <pubDate>Sun, 23 Jan 2022 19:44:42 +0900</pubDate>
    </item>
    <item>
      <title>[Java] Chapter 1. Java 개발 환경 구축</title>
      <link>https://kangworld.tistory.com/198</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dqxyQi/btrqT0VTF0k/1alEeKj75VS3FZO8pjqyT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dqxyQi/btrqT0VTF0k/1alEeKj75VS3FZO8pjqyT1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dqxyQi/btrqT0VTF0k/1alEeKj75VS3FZO8pjqyT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdqxyQi%2FbtrqT0VTF0k%2F1alEeKj75VS3FZO8pjqyT1%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;400&quot; height=&quot;250&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&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;h1&gt;&lt;span&gt;✍️ Java 개발 환경 구축하기&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바 프로그램을 실행하기 위해선 자바 실행환경 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;JRE(Java Runtime Environment)&lt;/span&gt;만 있으면 되지만 자바 개발을 위해선 반드시 JDK가 필요하다. JRE는 JVM(Java Virtual Machine), Java 클래스 라이브러리 등이 포함되어 자바 프로그램이 실행될 환경을 제공한다.&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;background-color: #f6e199;&quot;&gt;JDK&lt;/span&gt;를 설치한다는 의미이다. JDK는 Java Development Kit의 줄임말로, &lt;span style=&quot;background-color: #f6e199;&quot;&gt;JDK는 JRE를 포함한 자바 컴파일러(javac)와 jdb, javadoc와 같은 도구가 포함&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;Java로 소프트웨어를 만들기 위해선 개발 환경이 구축돼야 한다. 개발 환경은 다른 말로 플랫폼이라 하며 플랫폼에 따라 구축 방법이 다르다. 대표적인 개발 환경으로 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;JavaSE&lt;/span&gt;, JavaEE, JavaME, Java Android가 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JavaSE는 Java Standard Edition의 약자로 자바 프로그램을 만들기 위한 모든 개발 환경은 JavaSE로 시작하며 여기에 추가적인 API를 붙여서 기능을 확장할 때 우리는 이것을 JavaEE, JavaME라고 부른다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JavaEE는 웹 프로그래밍에 필요한 API가 포함되어 있고 JavaME는 임베디드 프로그램에 필요한 API가 포함되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JDK를 설치하면 JavaSE 개발 환경이 구축되고 JavaSE의 바운더리 내에 있는 Java 프로그램을 만들 수 있다. 참고로 웹 프로그래밍을 위해선 JavaEE 개발 환경을 구축해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본 포스팅에선 JDK를 다운받아 JavaSE 개발 환경을 구축하려 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;  JDK와 Eclipse 다운받기&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Windows64 기준으로&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.oracle.com/java/technologies/downloads/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;JDK 다운로드&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.eclipse.org/downloads/download.php?file=/technology/epp/downloads/release/2021-12/R/eclipse-jee-2021-12-R-win32-x86_64.zip&amp;amp;mirror_id=1248&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Eclipse 다운로드&lt;/a&gt;, Java 코딩에 사용할 IDE로 Eclipse를 사용한다. 그냥 Visual Studio 같은 녀석으로 코딩하고 컴파일도 하고 실행도 하고 디버깅도 할 수 있는 툴이다.&lt;/p&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. JDK를 설치해 준다. 놀랍게도 금방 끝난다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;269&quot; data-origin-height=&quot;47&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ua003/btrqXw7LSFv/T1Hn9lfWKWkDwgRmbWtrG1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ua003/btrqXw7LSFv/T1Hn9lfWKWkDwgRmbWtrG1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ua003/btrqXw7LSFv/T1Hn9lfWKWkDwgRmbWtrG1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fua003%2FbtrqXw7LSFv%2FT1Hn9lfWKWkDwgRmbWtrG1%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;47&quot; data-origin-width=&quot;269&quot; data-origin-height=&quot;47&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Windows + r 키를 눌러 실행창을 열어주고 sysdm.cpl을 입력&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;422&quot; data-origin-height=&quot;216&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/csCC4i/btrq1gJGC7w/BIYaoWv8z6IlZQvARhdH80/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/csCC4i/btrq1gJGC7w/BIYaoWv8z6IlZQvARhdH80/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/csCC4i/btrq1gJGC7w/BIYaoWv8z6IlZQvARhdH80/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcsCC4i%2Fbtrq1gJGC7w%2FBIYaoWv8z6IlZQvARhdH80%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;422&quot; height=&quot;216&quot; data-origin-width=&quot;422&quot; data-origin-height=&quot;216&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;3. 고급 -&amp;gt; 환경 변수&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;478&quot; data-origin-height=&quot;533&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwz5P0/btrq34IwOBn/GPFKJe3yJFE239ZKQ31FE1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwz5P0/btrq34IwOBn/GPFKJe3yJFE239ZKQ31FE1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwz5P0/btrq34IwOBn/GPFKJe3yJFE239ZKQ31FE1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbwz5P0%2Fbtrq34IwOBn%2FGPFKJe3yJFE239ZKQ31FE1%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;533&quot; data-origin-width=&quot;478&quot; data-origin-height=&quot;533&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;4. Path 클릭 -&amp;gt; 편집 클릭&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;449&quot; data-origin-height=&quot;575&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bYiiZm/btrqRZ3iT4z/0YImHkgvnGh2K1Xy6i9f30/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bYiiZm/btrqRZ3iT4z/0YImHkgvnGh2K1Xy6i9f30/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bYiiZm/btrqRZ3iT4z/0YImHkgvnGh2K1Xy6i9f30/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbYiiZm%2FbtrqRZ3iT4z%2F0YImHkgvnGh2K1Xy6i9f30%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;449&quot; height=&quot;575&quot; data-origin-width=&quot;449&quot; data-origin-height=&quot;575&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;5. C:\Program Files\Java\jdk-&lt;span style=&quot;background-color: #f6e199;&quot;&gt;17.0.1&lt;/span&gt;\bin를 환경 변수에 추가하기.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;17.0.1&lt;/span&gt;는 버전에 따라 다를 수 있으니 본인의 jdk 버전에 맞게 입력하시면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;533&quot; data-origin-height=&quot;507&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/drDspQ/btrqY9LkZfc/lqVyf2EM5B8NEhvLudAvp0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/drDspQ/btrqY9LkZfc/lqVyf2EM5B8NEhvLudAvp0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/drDspQ/btrqY9LkZfc/lqVyf2EM5B8NEhvLudAvp0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdrDspQ%2FbtrqY9LkZfc%2FlqVyf2EM5B8NEhvLudAvp0%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;533&quot; height=&quot;507&quot; data-origin-width=&quot;533&quot; data-origin-height=&quot;507&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;6. 이클립스를 다운로드하고 압축을 풀고 실행한다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;261&quot; data-origin-height=&quot;76&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ww8h1/btrq0RwAGg8/GQZWzOtejFyIkCd8yruha0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ww8h1/btrq0RwAGg8/GQZWzOtejFyIkCd8yruha0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ww8h1/btrq0RwAGg8/GQZWzOtejFyIkCd8yruha0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWw8h1%2Fbtrq0RwAGg8%2FGQZWzOtejFyIkCd8yruha0%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;261&quot; height=&quot;76&quot; data-origin-width=&quot;261&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7. File -&amp;gt; New -&amp;gt; Project 클릭&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;614&quot; data-origin-height=&quot;552&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b0imJL/btrq12K9RJz/HoXtmFd4WHCFins4iilQc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b0imJL/btrq12K9RJz/HoXtmFd4WHCFins4iilQc0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b0imJL/btrq12K9RJz/HoXtmFd4WHCFins4iilQc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb0imJL%2Fbtrq12K9RJz%2FHoXtmFd4WHCFins4iilQc0%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;614&quot; height=&quot;552&quot; data-origin-width=&quot;614&quot; data-origin-height=&quot;552&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;8. Java Proejct 선택&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;509&quot; data-origin-height=&quot;487&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DjbjL/btrqUSv8ZRz/odMnIktdYTtKtlnQNED1AK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DjbjL/btrqUSv8ZRz/odMnIktdYTtKtlnQNED1AK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DjbjL/btrqUSv8ZRz/odMnIktdYTtKtlnQNED1AK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDjbjL%2FbtrqUSv8ZRz%2FodMnIktdYTtKtlnQNED1AK%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;509&quot; height=&quot;487&quot; data-origin-width=&quot;509&quot; data-origin-height=&quot;487&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;9. JavaHello 프로젝트 생성&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;730&quot; data-origin-height=&quot;709&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FpRrg/btrqRZ97bgs/RyWoDyLB2ryAehgmj10un1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FpRrg/btrqRZ97bgs/RyWoDyLB2ryAehgmj10un1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FpRrg/btrqRZ97bgs/RyWoDyLB2ryAehgmj10un1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFpRrg%2FbtrqRZ97bgs%2FRyWoDyLB2ryAehgmj10un1%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;709&quot; data-origin-width=&quot;730&quot; data-origin-height=&quot;709&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;10. src 오른쪽 클릭 -&amp;gt; New -&amp;gt; Class 클릭&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;607&quot; data-origin-height=&quot;330&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6Ne7E/btrqY8ZVdye/tdrklucrfk1slEhmDk0Kkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6Ne7E/btrqY8ZVdye/tdrklucrfk1slEhmDk0Kkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6Ne7E/btrqY8ZVdye/tdrklucrfk1slEhmDk0Kkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6Ne7E%2FbtrqY8ZVdye%2Ftdrklucrfk1slEhmDk0Kkk%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;607&quot; height=&quot;330&quot; data-origin-width=&quot;607&quot; data-origin-height=&quot;330&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;11. 성의 없지만 클래스 명은 JavaTest로 만들어주고 main 메서드를 클래스 내부에 포함하도록 만든다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;562&quot; data-origin-height=&quot;648&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/evXO3i/btrq35HruDu/dKUYkx9vPPOISNsYGBYYZK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/evXO3i/btrq35HruDu/dKUYkx9vPPOISNsYGBYYZK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/evXO3i/btrq35HruDu/dKUYkx9vPPOISNsYGBYYZK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FevXO3i%2Fbtrq35HruDu%2FdKUYkx9vPPOISNsYGBYYZK%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;562&quot; height=&quot;648&quot; data-origin-width=&quot;562&quot; data-origin-height=&quot;648&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;12. main 메서드 내에 System.out.println(&quot;hello world!&quot;);를 입력하고 실행하면 콘솔 창에 hello world!가 출력된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;System.out.println()를 빠르게 작성하는 팁은 syso를 입력하고 Ctrl + Space를 누르면 자동완성된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;381&quot; data-origin-height=&quot;238&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tsSop/btrqVwfYazl/xncmQKKMkEmiq40lPhtvek/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tsSop/btrqVwfYazl/xncmQKKMkEmiq40lPhtvek/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tsSop/btrqVwfYazl/xncmQKKMkEmiq40lPhtvek/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtsSop%2FbtrqVwfYazl%2FxncmQKKMkEmiq40lPhtvek%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;381&quot; height=&quot;238&quot; data-origin-width=&quot;381&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;&amp;nbsp;&lt;/p&gt;</description>
      <category>Java/Java</category>
      <author>Kangworld</author>
      <guid isPermaLink="true">https://kangworld.tistory.com/198</guid>
      <comments>https://kangworld.tistory.com/198#entry198comment</comments>
      <pubDate>Tue, 18 Jan 2022 00:43:48 +0900</pubDate>
    </item>
    <item>
      <title>[C#] Task.FromResult 이해하기</title>
      <link>https://kangworld.tistory.com/195</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;img.png&quot; data-origin-width=&quot;568&quot; data-origin-height=&quot;568&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bsOfa2/btroMphiDY0/qYOaxlgyaIkxYiEwosKPmK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bsOfa2/btroMphiDY0/qYOaxlgyaIkxYiEwosKPmK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bsOfa2/btroMphiDY0/qYOaxlgyaIkxYiEwosKPmK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbsOfa2%2FbtroMphiDY0%2FqYOaxlgyaIkxYiEwosKPmK%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;300&quot; height=&quot;300&quot; data-filename=&quot;img.png&quot; data-origin-width=&quot;568&quot; data-origin-height=&quot;568&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h1&gt;&lt;span&gt;✍️ Task.FromResult&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;await 키워드를 만나면 세 가지 경우의 수가 발생한다.&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: #000000;&quot;&gt;awaitable이 실행 과정에서 예외를 발생하면 &lt;span style=&quot;color: #000000;&quot;&gt;exception을 던진다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;awaitable&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;간단하게 Task 또는 Task&amp;lt;T&amp;gt;를 반환하는 함수(+메서드)라고 생각하면 된다.&amp;nbsp;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;+awaitable은 void도 반환하지만 Task를 반환하는 awaitable과는 결이 다르다고 한다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;자세한 내용은 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;a style=&quot;color: #006dd7;&quot; href=&quot;https://docs.hhvm.com/hack/asynchronous-operations/awaitables&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;링크&lt;/a&gt;&lt;/span&gt;를 참조하길 바랍니다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;두 번째&lt;/span&gt;, awaitable이 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;이미 완료된 Task&lt;/span&gt;라면 async 메서드를,&amp;nbsp;마치 일반&amp;nbsp;메서드처럼,&amp;nbsp;동기 방식으로 계속 실행한다.&amp;nbsp;&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;&lt;span style=&quot;color: #000000;&quot;&gt;세 번째,&amp;nbsp;awaitable이 끝나지 않았다면 작업이 끝난 후 await 이후의 나머지 코드를 실행하도록 대기 작업으로 등록하고&amp;nbsp; async 메서드의 호출자에게 Task를 반환한다.&amp;nbsp;&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;&lt;span style=&quot;color: #000000;&quot;&gt;본 포스팅은 Task.FromResult를 다루며 이는 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;두 번째&lt;/span&gt; 상황과 관련이 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Task 클래스 내부의 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;CompletedTask&lt;/span&gt;&amp;nbsp;프로퍼티는 작업이 완료된 더미 Task를 반환한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나아가 완료된 더미 Task에 어떠한 값을 실어서 보내고 싶다면 특정 메서드를 사용하면 되는데 이게 바로 본 포스티에서 다룰 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;Task.FromResult&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 style=&quot;color: #000000;&quot;&gt;상식선에서 생각해 봤을 때 완료된 Task는 비동기식으로 실행할 필요가 없다. 왜냐? 이미 끝났으니 말이다.&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 style=&quot;color: #000000;&quot;&gt;그렇다면 Task 클래스는 어떤 이유로 완료된 더미 Task를 제공하는 걸까?&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;h1&gt;&lt;span&gt;  Task.FromResult와 캐싱&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 비동기적으로 서버로부터 유저의 닉네임을 받아오는 GET 메서드가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자 K 씨는 A라는 유저의 닉네임이 필요해 GET 메서드를 호출했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;K 씨는 이것저것 코드를 작성하다가 다시 유저 A의 닉네임이 필요해졌다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반복적으로 유저 A의 닉네임이 필요한 상황... 개발자 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;첫 번째, 다시 GET 메서드를 호출해서 닉네임을 얻어오면 매우 쉽다. 그런데 서버로부터 어떤 값을 얻어오는 작업엔 적지 않은 시간이 걸리므로 이를 반복적으로 수행하는 건 문제가 있어보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번째, 일종의 캐싱으로 딕셔너리를 선언해서 유저의 닉네임이 딕셔너리에 없다면 1회에 한정해 값을 서버로부터 받아와 딕셔너리에 저장한다. 이후에 유저의 닉네임이 필요하면 딕셔너리에서 값을 빼오면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다르게 말하면, GET 메서드 내부에서 키에 해당하는 Value가 있다면 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;Task.FromResult&lt;/span&gt; 메서드를 이용해 값을 즉시 반환하면 되고 없다면 비동기적으로 서버로부터 값을 얻어오면 된다. (데이터의 동기화에 관한 문제는 남아있지만 괜찮은 해결법 같다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;  예제 코드&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MS Docs에 좋은 예제가 있어서 가져왔다.&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 style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.microsoft.com/ko-kr/dotnet/standard/parallel-programming/how-to-create-pre-computed-tasks&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;예제 코드 링크&lt;/a&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1640276433402&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;using System;

namespace AsyncEX
{
    using System;
    using System.Collections.Concurrent;
    using System.Diagnostics;
    using System.Linq;
    using System.Net;
    using System.Threading.Tasks;

    class CachedDownloads
    {
        // 다운로드된 결과물 저장하는 캐시
        static ConcurrentDictionary&amp;lt;string, string&amp;gt; cachedDownloads =
           new ConcurrentDictionary&amp;lt;string, string&amp;gt;();

        // 요청을 비동기적 혹은 동기적으로 수행하는 메서드
        public static Task&amp;lt;string&amp;gt; DownloadStringAsync(string address)
        {
            // 캐시에 존재하는지 확인
            string content;
            if (cachedDownloads.TryGetValue(address, out content))
            {
                return Task.FromResult&amp;lt;string&amp;gt;(content);
            }

            // 캐시에 없다면 다운하고 캐시에 저장
            return Task.Run(async () =&amp;gt;
            {
                content = await new WebClient().DownloadStringTaskAsync(address);
                cachedDownloads.TryAdd(address, content);
                return content;
            });
        }

        static void Main(string[] args)
        {
            // 다운받을 URL 주소
            string[] urls = new string[]
            {
         &quot;http://www.naver.com&quot;,
         &quot;http://www.google.com&quot;
            };

            Stopwatch stopwatch = new Stopwatch();

           
            stopwatch.Start();
            var downloads = from url in urls
                            select DownloadStringAsync(url);
            
            // 캐시를 활용하지 못하는 케이스
            Task.WhenAll(downloads).ContinueWith(results =&amp;gt;
            {
                stopwatch.Stop();
                Console.WriteLine(&quot;Retrieved {0} characters. Elapsed time was {1} ms.&quot;,
                   results.Result.Sum(result =&amp;gt; result.Length),
                   stopwatch.ElapsedMilliseconds);
            })
            .Wait();

            // 캐시를 활용하는 케이스
            stopwatch.Restart();
            downloads = from url in urls
                        select DownloadStringAsync(url);
            Task.WhenAll(downloads).ContinueWith(results =&amp;gt;
            {
                stopwatch.Stop();

                Console.WriteLine(&quot;Retrieved {0} characters. Elapsed time was {1} ms.&quot;,
                   results.Result.Sum(result =&amp;gt; result.Length),
                   stopwatch.ElapsedMilliseconds);
            })
            .Wait();
        }
    }

}&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;팁은 아래와 같이 Task.Run과 람다식을 이용하면 동기 메서드 내에서 비동기 메서드를 호출할 수 있다는 것이다!&lt;/p&gt;
&lt;pre id=&quot;code_1640276978597&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;return Task.Run(async () =&amp;gt;
{
      content = await new WebClient().DownloadStringTaskAsync(address);
      cachedDownloads.TryAdd(address, content);
      return content;
});&lt;/code&gt;&lt;/pre&gt;</description>
      <category>C#</category>
      <author>Kangworld</author>
      <guid isPermaLink="true">https://kangworld.tistory.com/195</guid>
      <comments>https://kangworld.tistory.com/195#entry195comment</comments>
      <pubDate>Fri, 24 Dec 2021 01:33:31 +0900</pubDate>
    </item>
    <item>
      <title>[C#] C#은 모든 자료형이 객체일까?</title>
      <link>https://kangworld.tistory.com/190</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;568&quot; data-origin-height=&quot;568&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfbz31/btrnR2Pzhut/R1GZjNZm5xBwaCgL5qnuT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfbz31/btrnR2Pzhut/R1GZjNZm5xBwaCgL5qnuT1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfbz31/btrnR2Pzhut/R1GZjNZm5xBwaCgL5qnuT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbfbz31%2FbtrnR2Pzhut%2FR1GZjNZm5xBwaCgL5qnuT1%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;300&quot; height=&quot;300&quot; data-origin-width=&quot;568&quot; data-origin-height=&quot;568&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h1&gt;&lt;span style=&quot;color: #000000;&quot;&gt;❓ C#은 모든 자료형이 객체일까?&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;한 가지 의문으로부터 본 포스팅은 시작한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;오늘 문득 생각에 잠겨 C#은 진짜 모든 자료형을 객체로 취급할까?라는 의문을 품고 직접 검증에 나섰다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;결론부터 말하면 &lt;/span&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;모든&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 자료형을 객체로 취급한다는 말은 엄연히 틀리며 해석하기에 두 가지 답을 제시할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;h1&gt;&lt;span style=&quot;color: #000000;&quot;&gt;✍️ int는 객체인가?&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;C#에서 모든 자료형은 객체인가를 알기 전에 int는 객체인가 대한 정답을 내리면 정답에 한걸음 다가갈 수 있지 않을까?&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;기본적으로 C#에선 class와 String(string)은 &lt;/span&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Reference-type&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이며 &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;struct와 enum은 &lt;/span&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Value-type&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;여기서 한 가지 중요한 사실은 int와 같은 원시 타입은 &lt;/span&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;구조체&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;로 구현했단 점이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;360&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dw5RjC/btrnTCQCoeI/NY2kxcI7e6QRK0wTZtkji0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dw5RjC/btrnTCQCoeI/NY2kxcI7e6QRK0wTZtkji0/img.png&quot; data-alt=&quot;https://keumjae.tistory.com/177&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dw5RjC/btrnTCQCoeI/NY2kxcI7e6QRK0wTZtkji0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdw5RjC%2FbtrnTCQCoeI%2FNY2kxcI7e6QRK0wTZtkji0%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;640&quot; height=&quot;360&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;360&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://keumjae.tistory.com/177&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;int는 구조체로 구현했다.&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 굉장히 많은 의미를 담고 있는 문장이다. 왜냐하면 많은 사람들이 C#의 구조체는 상속받을 수 없으며 상속할 수도 없는 자료형으로 알고 있기 때문이다. 그럼에도 불구하고 int는 &lt;/span&gt;&lt;a href=&quot;https://docs.microsoft.com/ko-kr/dotnet/api/system.valuetype?view=net-6.0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;System.ValueType&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이란 추상 클래스를 상속받고 있다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;코드를 작성해 보면 int는 최상위 클래스 object가 제공하는 ToString 메서드를 사용 가능한 점을 고려했을 때 int(구조체)가 System.ValueType을 상속받는 것은 자명해 보인다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;238&quot; data-origin-height=&quot;306&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JizQy/btrnQ4zLZEG/1xMk7fKSGPESRFfkW4aVdK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JizQy/btrnQ4zLZEG/1xMk7fKSGPESRFfkW4aVdK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JizQy/btrnQ4zLZEG/1xMk7fKSGPESRFfkW4aVdK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJizQy%2FbtrnQ4zLZEG%2F1xMk7fKSGPESRFfkW4aVdK%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;238&quot; height=&quot;306&quot; data-origin-width=&quot;238&quot; data-origin-height=&quot;306&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;다만 여전히 의구심이 든다. 정말 구조체가 상속을 받을 수 있는 거야?&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;여기에 저명한 C# 개발자 &lt;/span&gt;&lt;a href=&quot;https://stackoverflow.com/users/88656/eric-lippert&quot; target=&quot;_self&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;Eric Lippert&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot;color: #000000;&quot;&gt;가 stackoverflow에 &lt;/span&gt;&lt;a href=&quot;https://stackoverflow.com/questions/1682231/how-do-valuetypes-derive-from-object-referencetype-and-still-be-valuetypes&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;답변&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot;color: #000000;&quot;&gt;을 남겼다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;간략하게 정리하자면 구조체가 클래스를 상속받는 것은 가능하며 &lt;/span&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;모든 구조체&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;는 System.Object로부터 파생된 &lt;/span&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;System.ValueType 클래스를 암시적으로 상속받고&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 있다. 따라서 사용자 정의 구조체를 비롯한 모든 구조체는 임의의 클래스로부터 상속받는것은 명백히 불가능하다. &lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;추가 자료 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;C# language spec, 4.1.1:&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;4.1.1 The System.ValueType type&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;All value types implicitly inherit from the class System.ValueType, which, in turn, inherits from class object. It is not possible for any type to derive from a value type, and value types are thus implicitly sealed (&amp;sect;10.1.1.2).&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Then, later (4.1.3) struct is explicitly defined to be a value type:&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;4.1.3 Struct types&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;A struct type is a value type that can declare constants, fields, methods, properties, indexers, operators, instance constructors, static constructors, and nested types.&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000;&quot;&gt; &amp;nbsp;그렇다면 모든것들이 객체일까?&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;다시 원래 궁금증으로 돌아오면, 그렇다면 C#에서 모든 것은 객체일까?&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;여기에 대한 &lt;/span&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;두 가지 해석&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 존재한다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;해석이 갈리는 이유는 C#에서 객체라는 단어가 다소 모호한데, .NET framework에서 정의한 System.Object의 별칭이 &lt;/span&gt;&lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;객체(object)&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이며 또한 동적으로 생성된 클래스의 인스턴스도 &lt;/span&gt;&lt;span style=&quot;background-color: #99cefa;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;객체&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;라고 부르기 때문이다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;첫 째로 상속적인 측면에서 모든 것을 객체라고 묻는다면 &quot;&lt;/span&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;거의 모든 것들&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&quot;은 객체이다. &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;모든 &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Value-type&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;과 클래스, 배열, 델리게이트는 &lt;/span&gt;&lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;System.Object&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;를 상속받음으로써 &lt;/span&gt;&lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;객체&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이나 인터페이스 타입과 포인터는 System.Object을 상속받지 않아 객체라고 말할 순 없다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이처럼 거의 모든 타입이 System.Object를 상속받아 객체라고말 할 수 있지만 여전히 레퍼런스 타입과 벨류타입이 다뤄지는 혹은 동작하는 방식이 매우 다르기에 이는 두번째 해석으로 이어진다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;두 번째로 &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Reference-type과&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Value-type&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;의 관점으로 본다면 명백히 모든 것은 &lt;/span&gt;&lt;span style=&quot;background-color: #99cefa;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;객체&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;가 아니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Value-type&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 System.Object를 상속받으나 그것들이 &lt;/span&gt;&lt;span style=&quot;background-color: #99cefa;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Reference-type&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #99cefa;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;(객체)&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;과 전혀 다르게 취급된다. &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;예를 들면 &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Value-type&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;은 데이터를 자체적으로 할당된 메모리에 저장하는 반면 &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Reference-type&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;은 실제 데이터가 할당된 주소 공간을 가리킨다. 그뿐만 아니라 다른 변수에 값을 대입하는 과정도 이 둘은 너무나 다른 결과를 야기한다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;즉, &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Value-type&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;과 &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Reference-type은&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 전혀 다르게 다뤄지며 &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Value-type&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;을 명시적으로 박싱(Boxing)해야만 &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Reference-type&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;으로 변환된다. &lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;c++ arduino&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;c++&quot;&gt;&lt;code&gt;int num = 23; // 23이 num에 할당됨 
Object obj = num; // 박싱(Boxing)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;601&quot; data-origin-height=&quot;361&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CkGuR/btrnQjxxizv/Z4xNTgF3uwZL17suqccv11/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CkGuR/btrnQjxxizv/Z4xNTgF3uwZL17suqccv11/img.jpg&quot; data-alt=&quot;https://www.partech.nl/nl/publicaties/2021/07/what-is-boxing-and-unboxing-in-c-sharp#&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CkGuR/btrnQjxxizv/Z4xNTgF3uwZL17suqccv11/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCkGuR%2FbtrnQjxxizv%2FZ4xNTgF3uwZL17suqccv11%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;601&quot; height=&quot;361&quot; data-origin-width=&quot;601&quot; data-origin-height=&quot;361&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://www.partech.nl/nl/publicaties/2021/07/what-is-boxing-and-unboxing-in-c-sharp#&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;  결론&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;C#은&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 모든 자료형을 객체로 취급하지 않는다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;거의 모든 자료형은 System.Object를 상속받는다는 표현이 적합하다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>C#</category>
      <author>Kangworld</author>
      <guid isPermaLink="true">https://kangworld.tistory.com/190</guid>
      <comments>https://kangworld.tistory.com/190#entry190comment</comments>
      <pubDate>Wed, 15 Dec 2021 05:25:28 +0900</pubDate>
    </item>
    <item>
      <title>[Unity] 절차적 애니메이션(Procedural animation) #2</title>
      <link>https://kangworld.tistory.com/189</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;300&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HfEdd/btrnFaM6exw/lROHIgKvCW9nz9yrQlIJI0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HfEdd/btrnFaM6exw/lROHIgKvCW9nz9yrQlIJI0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HfEdd/btrnFaM6exw/lROHIgKvCW9nz9yrQlIJI0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHfEdd%2FbtrnFaM6exw%2FlROHIgKvCW9nz9yrQlIJI0%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;300&quot; height=&quot;300&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;300&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h1&gt;&lt;span&gt;  절차적 애니메이션 #1&lt;/span&gt;&lt;/h1&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;a style=&quot;color: #006dd7;&quot; href=&quot;https://kangworld.tistory.com/187&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[Unity] 절차적 애니메이션(Procedural animation) #1&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1639305910387&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Unity] 절차적 애니메이션(Procedural animation) #1&quot; data-og-description=&quot;✍️ 절차적 애니메이션이란? 대부분의 게임에서 캐릭터 애니메이션은 정적이다. 캐릭터가 움직일 때 필요한 동작은 아티스트들이 사전에 만들어놓은 애니메이션을 재생하는 것으로 다른 움직&quot; data-og-host=&quot;kangworld.tistory.com&quot; data-og-source-url=&quot;https://kangworld.tistory.com/187&quot; data-og-url=&quot;https://kangworld.tistory.com/187&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bEh26T/hyMFOksg5x/sN89CxPhiiH1ae9Ph191H0/img.png?width=300&amp;amp;height=300&amp;amp;face=0_0_300_300,https://scrap.kakaocdn.net/dn/dNvbsq/hyMFRhaTPW/DB1Pnk9DvalR2F1jCkUntk/img.png?width=300&amp;amp;height=300&amp;amp;face=0_0_300_300,https://scrap.kakaocdn.net/dn/o5HAV/hyMFPjnB6o/xytceZFu8mRd2sxIILbGJ0/img.png?width=1909&amp;amp;height=536&amp;amp;face=0_0_1909_536&quot;&gt;&lt;a href=&quot;https://kangworld.tistory.com/187&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://kangworld.tistory.com/187&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bEh26T/hyMFOksg5x/sN89CxPhiiH1ae9Ph191H0/img.png?width=300&amp;amp;height=300&amp;amp;face=0_0_300_300,https://scrap.kakaocdn.net/dn/dNvbsq/hyMFRhaTPW/DB1Pnk9DvalR2F1jCkUntk/img.png?width=300&amp;amp;height=300&amp;amp;face=0_0_300_300,https://scrap.kakaocdn.net/dn/o5HAV/hyMFPjnB6o/xytceZFu8mRd2sxIILbGJ0/img.png?width=1909&amp;amp;height=536&amp;amp;face=0_0_1909_536');&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;[Unity] 절차적 애니메이션(Procedural animation) #1&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;kangworld.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;h1&gt;&lt;span&gt;✍️ Two Bone IK Constraint&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다양한 동작들을 유도할 수 있는 서로 다른 제약조건(Constraints)이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파트 1에서 알아본 &lt;span style=&quot;background-color: #c0d1e7;&quot;&gt;Multi-Aim Constraint&lt;/span&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;background-color: #f6e199;&quot;&gt;Two Bone IK Constraint&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;Two Bone IK Constraint를 사용하기 전에 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;IK&lt;/span&gt;라는 단어가 굉장히 낯설어 이에 대해 정리하는 것으로 시작하겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;  Inverse Kinematics(IK, 역 기구학)&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Forward Kinematics과 Inverse Kinematics는 로봇공학에서 주로 등장하는 용어로&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;background-color: #f6e199;&quot;&gt;Forward Kinematics&lt;/span&gt;란 관절 각도(&lt;span style=&quot;background-color: #ffffff; color: #202124;&quot;&gt;&amp;theta;)&lt;/span&gt;를 조절해서 로봇의 손이 목표점에 도달하게 하는 것으로 쉽게 말하면 &lt;span style=&quot;background-color: #f6e199;&quot;&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 style=&quot;background-color: #f6e199;&quot;&gt;Inverse Kinematics&lt;/span&gt;란 목표점이 주어지면 손이 목표에 도달하기 위한 관절 1과 관절 2의 각도가 산출되는 것으로 쉽게 말하면 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;손끝의 위치를 관절의 각도로 변환하는 수학적 계산법을 의미한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 Inverse Kinematics는 목표점 A가 주어지면 관절의 각도 &lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #202124;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #202124;&quot;&gt;&amp;theta; 1과 &lt;span style=&quot;background-color: #ffffff; color: #202124;&quot;&gt;&amp;theta; 2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&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;widthOrigin&quot; data-origin-width=&quot;429&quot; data-origin-height=&quot;345&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c7hYkt/btrnL72OuVt/YxottKpp9ORzM0DajKSJK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c7hYkt/btrnL72OuVt/YxottKpp9ORzM0DajKSJK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c7hYkt/btrnL72OuVt/YxottKpp9ORzM0DajKSJK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc7hYkt%2FbtrnL72OuVt%2FYxottKpp9ORzM0DajKSJK0%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;429&quot; height=&quot;345&quot; data-origin-width=&quot;429&quot; data-origin-height=&quot;345&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;h1 id=&quot; ️-애니메이션-리깅-실습&quot;&gt;&lt;span&gt; ️ 다시&amp;nbsp;&lt;span&gt;Two Bone IK Constraint&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Inverse Kinematics의 정의를 고려해 Two Bone IK Constraint가 무엇인지 예상해 보면, Two Bone IK Constraint는 목표점이 주어졌을 때 두 개의 뼈대가 유기적으로 움직이도록 하는 제약조건이다.&lt;/p&gt;
&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;Hierarchy 창에서 던전 스켈레톤을 클릭하고 Animation Rigging - Rig Setup을 클릭하면 Rig 2 오브젝트가 생성된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;803&quot; data-origin-height=&quot;354&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bx7tv5/btrnFa0FYSB/DDBB2Zgv2rU2wsDnZtNMx0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bx7tv5/btrnFa0FYSB/DDBB2Zgv2rU2wsDnZtNMx0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bx7tv5/btrnFa0FYSB/DDBB2Zgv2rU2wsDnZtNMx0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbx7tv5%2FbtrnFa0FYSB%2FDDBB2Zgv2rU2wsDnZtNMx0%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;803&quot; height=&quot;354&quot; data-origin-width=&quot;803&quot; data-origin-height=&quot;354&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;span style=&quot;background-color: #99cefa;&quot;&gt;Rig 1 -&amp;gt; HeadRig&lt;/span&gt;, &lt;span style=&quot;background-color: #ffc9af;&quot;&gt;Rig 2 -&amp;gt; ArmRig&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;ArmRig의 자식으로 Empty 오브젝트를 하나 만들어서 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;Two Bone IK Constraint&lt;/span&gt; 컴포넌트를 추가하자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;857&quot; data-origin-height=&quot;360&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cnpYcm/btrnFasTffI/ZJzJPqHvl7dJzW8EbjRH81/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cnpYcm/btrnFasTffI/ZJzJPqHvl7dJzW8EbjRH81/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cnpYcm/btrnFasTffI/ZJzJPqHvl7dJzW8EbjRH81/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcnpYcm%2FbtrnFasTffI%2FZJzJPqHvl7dJzW8EbjRH81%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;857&quot; height=&quot;360&quot; data-origin-width=&quot;857&quot; data-origin-height=&quot;360&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;Two Bone IK Constraint 컴포넌트에 세 개의 본 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;Root, Mid, Tip&lt;/span&gt;을 넘겨줘야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;Two Bone&lt;/span&gt; IK Constraint임에도 세 개의 본을 넘겨주는 이유는 아마도 세 개의 관절을 얻기 위함인 것 같다.(Two Bones = Three Joints)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본 포스팅에선 Root에 UpperArm(상완), Mid에 ForeArm(전완), Tip에 Hand(손)을 넘겨줘서 팔을 유기적으로 움직여 볼 예정이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;326&quot; data-origin-height=&quot;261&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzp0AK/btrnCQVCHnw/5j24FKVLRamhUGFQI9fsP0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzp0AK/btrnCQVCHnw/5j24FKVLRamhUGFQI9fsP0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzp0AK/btrnCQVCHnw/5j24FKVLRamhUGFQI9fsP0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbzp0AK%2FbtrnCQVCHnw%2F5j24FKVLRamhUGFQI9fsP0%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;326&quot; height=&quot;261&quot; data-origin-width=&quot;326&quot; data-origin-height=&quot;261&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;502&quot; data-origin-height=&quot;326&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cfUWBj/btrnGkhGbAM/g1Yx6HRSLqkAX7ayAGz460/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cfUWBj/btrnGkhGbAM/g1Yx6HRSLqkAX7ayAGz460/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cfUWBj/btrnGkhGbAM/g1Yx6HRSLqkAX7ayAGz460/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcfUWBj%2FbtrnGkhGbAM%2Fg1Yx6HRSLqkAX7ayAGz460%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;502&quot; height=&quot;326&quot; data-origin-width=&quot;502&quot; data-origin-height=&quot;326&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;이제 Source Objects를 넘겨줘야 할 차례로 ArmMover 산하에 Empty Object(Target)를 하나 만들어서 Source Object에 넘겨주자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저, Target을 처음 생성하면 바닥에 박혀있는 것을 확인할 수 있는데 Target의 위치를 L Hand에 배치해 보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Target을 클릭하고 Ctrl을 누른 상태에서 Bip001 L Hand를 클릭하고 상단의 Animation Rigging - Align Transform을 클릭하면 Target이 L Hand에 위치한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;345&quot; data-origin-height=&quot;385&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdTYMZ/btrnB7w3U7o/cdgCI33nfduuQfNSwKhoC0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdTYMZ/btrnB7w3U7o/cdgCI33nfduuQfNSwKhoC0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdTYMZ/btrnB7w3U7o/cdgCI33nfduuQfNSwKhoC0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdTYMZ%2FbtrnB7w3U7o%2FcdgCI33nfduuQfNSwKhoC0%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;345&quot; height=&quot;385&quot; data-origin-width=&quot;345&quot; data-origin-height=&quot;385&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;이제 실행해 보면 애니메이션을 재생하고 있음에도 불구하고 손은 제자리에 있다. 나아가 Target을 움직일 때마다 팔이 따라온다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;ezgif.com-gif-maker (12).gif&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;417&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qK9xM/btrnCQuAMZJ/3lgf62AuyyXxfqznmXAfkk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qK9xM/btrnCQuAMZJ/3lgf62AuyyXxfqznmXAfkk/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qK9xM/btrnCQuAMZJ/3lgf62AuyyXxfqznmXAfkk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/qK9xM/btrnCQuAMZJ/3lgf62AuyyXxfqznmXAfkk/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;800&quot; height=&quot;417&quot; data-filename=&quot;ezgif.com-gif-maker (12).gif&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;417&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;한 가지 팁은 게임을 실행하고 Target을 원하는 위치로 움직이고 Target - Transform - Copy World Placement 후 게임을 종료하고 Target - Transform - Paste World Placement를 하면 이전에 지정한 위치로 Target이 이동해 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 여러 가지로 응용이 가능한데, 예를 들면 무기를 잡는 위치를 지정할 때 유용하다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;ezgif.com-gif-maker (13).gif&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;417&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFnZsf/btrnGkILWfT/sXPjKTrzVomUh7UhKvqLGk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFnZsf/btrnGkILWfT/sXPjKTrzVomUh7UhKvqLGk/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFnZsf/btrnGkILWfT/sXPjKTrzVomUh7UhKvqLGk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bFnZsf/btrnGkILWfT/sXPjKTrzVomUh7UhKvqLGk/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;800&quot; height=&quot;417&quot; data-filename=&quot;ezgif.com-gif-maker (13).gif&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;417&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;추가로 하나의 Rig에 Constraint가 여러 개인 경우 Hierarchy 순서대로 적용된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본 포스팅을 예로 들면, HeadAim이 먼저 적용되고 다음으로 ChestAim이 적용된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;281&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgeYGy/btrnGkhJysi/AKb8TT3m2QAb0RyVNwZvuk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgeYGy/btrnGkhJysi/AKb8TT3m2QAb0RyVNwZvuk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgeYGy/btrnGkhJysi/AKb8TT3m2QAb0RyVNwZvuk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgeYGy%2FbtrnGkhJysi%2FAKb8TT3m2QAb0RyVNwZvuk%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;300&quot; height=&quot;281&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;281&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;h1&gt;&lt;span&gt;  마무리&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Unity에선 여러 Constraint를 사용한 예제를 제공하니 필요한 게 있다면 찾아서 공부할 수 있다!&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;802&quot; data-origin-height=&quot;557&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bm6bcU/btrnC38Vy3f/rVUmNe0Do1DipnHcknjPb0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bm6bcU/btrnC38Vy3f/rVUmNe0Do1DipnHcknjPb0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bm6bcU/btrnC38Vy3f/rVUmNe0Do1DipnHcknjPb0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbm6bcU%2FbtrnC38Vy3f%2FrVUmNe0Do1DipnHcknjPb0%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;802&quot; height=&quot;557&quot; data-origin-width=&quot;802&quot; data-origin-height=&quot;557&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 style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;참고 &lt;a id=&quot;video-title&quot; href=&quot;https://www.youtube.com/watch?v=Htl7ysv10Qs&quot;&gt;Make your Characters Interactive! - Animation Rigging in Unity&lt;/a&gt;&lt;/p&gt;</description>
      <category>Unity/Unity</category>
      <author>Kangworld</author>
      <guid isPermaLink="true">https://kangworld.tistory.com/189</guid>
      <comments>https://kangworld.tistory.com/189#entry189comment</comments>
      <pubDate>Mon, 13 Dec 2021 01:13:58 +0900</pubDate>
    </item>
  </channel>
</rss>