<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>혜성처럼 빛나는 정보</title>
    <link>https://comet-info.tistory.com/</link>
    <description>일상에서 발견할 수 있는 여러 가지 정보를 공유</description>
    <language>ko</language>
    <pubDate>Wed, 8 Apr 2026 11:27:00 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>comet observer</managingEditor>
    <image>
      <title>혜성처럼 빛나는 정보</title>
      <url>https://tistory1.daumcdn.net/tistory/6079649/attach/dfea65c6b6254713a871f069a65fffff</url>
      <link>https://comet-info.tistory.com</link>
    </image>
    <item>
      <title>도메인 모델 만들기</title>
      <link>https://comet-info.tistory.com/17</link>
      <description>&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;요구사항 쭉 정리해보기&lt;/li&gt;
&lt;li&gt;도메인 모델 만들기&lt;/li&gt;
&lt;/ul&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;듣고 배우기&lt;/li&gt;
&lt;li&gt;중요한 것들 찾기 (개념 식별)&lt;/li&gt;
&lt;li&gt;연결 고리 찾기 (관계 정의)&lt;/li&gt;
&lt;li&gt;것들을 설명하기 (속성 및 기본 행위 명시)&lt;/li&gt;
&lt;li&gt;그려보기 (시각화)&lt;/li&gt;
&lt;li&gt;이야기 하고 다듬기 (반복)&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;용어사전 정리&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://draw.io&quot;&gt;draw.io&lt;/a&gt;로 관계 그리기&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;엔티티&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;도메인 모델을 만들 때 사용하는 패턴&lt;/li&gt;
&lt;li&gt;도메인 안에 있는 대상이나 개념&lt;/li&gt;
&lt;li&gt;고유한 식별자를 가지고 이를 통해서 개별적으로 구분된다.&lt;/li&gt;
&lt;li&gt;생명주기를&lt;span&gt; &lt;/span&gt;가진다&lt;span&gt;. &lt;/span&gt;시간의&lt;span&gt; &lt;/span&gt;흐름에&lt;span&gt; &lt;/span&gt;따라&lt;span&gt; &lt;/span&gt;상태가&lt;span&gt; &lt;/span&gt;변화될&lt;span&gt; &lt;/span&gt;수&lt;span&gt; &lt;/span&gt;있다&lt;span&gt;.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>IT 개발/Clean Spring</category>
      <author>comet observer</author>
      <guid isPermaLink="true">https://comet-info.tistory.com/17</guid>
      <comments>https://comet-info.tistory.com/17#entry17comment</comments>
      <pubDate>Sat, 12 Jul 2025 17:44:34 +0900</pubDate>
    </item>
    <item>
      <title>IntelliJ 유용한 기능</title>
      <link>https://comet-info.tistory.com/16</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;IntelliJ&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Live Templates
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Settings -&amp;gt; Editor -&amp;gt; Live Templates -&amp;gt; Java -&amp;gt; + button -&amp;gt; Abbreviation &amp;amp; Description&lt;/li&gt;
&lt;li&gt;Applicable in Java: 어디서 등장할지 선택&lt;/li&gt;
&lt;li&gt;Template text examples&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1752309449283&quot; class=&quot;stylus&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@org.junit.jupiter.api.Test
void $TESTNAME$(){
	$END$
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1752309516413&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;org.assertj.core.api.Assertions.assertThat($ACTUAL$)$END$&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>IT 개발/Useful Function</category>
      <author>comet observer</author>
      <guid isPermaLink="true">https://comet-info.tistory.com/16</guid>
      <comments>https://comet-info.tistory.com/16#entry16comment</comments>
      <pubDate>Sat, 12 Jul 2025 17:39:31 +0900</pubDate>
    </item>
    <item>
      <title>성남시 70세 이상 어르신 버스요금 지원</title>
      <link>https://comet-info.tistory.com/15</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;성남시에서 만 70세 이상 어르신들의 버스요금을 지원하는 사업을 진행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;성남시에서 2022년 12월 보건복지부와 사회보장제도 신설 협의를 마치고, 2023년 2월 20일 제정한 성남시 어르신 교통비 지원 사업에 대해 알아보자.&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;제목을 입력해주세요_-001 (1).jpg&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CJ8Ww/btslDGsSvpn/dyMA1Rq1IPak2r5TVYfwS1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CJ8Ww/btslDGsSvpn/dyMA1Rq1IPak2r5TVYfwS1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CJ8Ww/btslDGsSvpn/dyMA1Rq1IPak2r5TVYfwS1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCJ8Ww%2FbtslDGsSvpn%2FdyMA1Rq1IPak2r5TVYfwS1%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;533&quot; height=&quot;533&quot; data-filename=&quot;제목을 입력해주세요_-001 (1).jpg&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;어르신 버스요금 지원 대상자&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;성남시에 거주하는 만 70세 이상의 어르신&lt;/b&gt;이 지원 대상자이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;성남시 거주의 기준은 성남시에 주민등록이 되어 있어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만 70세도 중요한 기준이다. 통념적으로 사용하던 나이 계산법 때문에 헷갈리지 않기를 바란다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(2023년 6월 28일부터 시행되는 '만 나이 통일법'에 대해서 알아보고 싶다면? -&amp;gt; &lt;a href=&quot;https://comet-info.tistory.com/14&quot;&gt;Here&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;어르신 버스요금 지원 범위&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;성남 시내에서 운행되거나 성남 시내를 경유하는 모든 광역버스, 시내버스, 마을버스가 지원 범위 대상이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지원 금액은 분기마다 5만 7500원이고 연간 최대 23만원이다. 해당 한도 내에서 결제된 요금만큼의 교통비를 지원한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;어르신 버스요금 지원하는 방법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;성남지역 농협에서 기존에 발급해주던 경기도 우대용 교통카드인 G-PASS 교통카드를 신규로 받거나 재발급 받아야 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;신규 혹은 재발급 받은 G-PASS 교통카드로 버스 요금을 결제하면, 6월 사용분부터 분기별로 정산해 그 다음 분기 첫 달 말에 대상자 계좌로 지급한다. 다만, 첫 지원금은 7월 말에 지급된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7월 말에 지급되는 첫 지원금을 제외하고, 예를 들자면, 3분기인 7월, 8월, 9월에 G-PASS 교통카드를 이용해 사용한 버스 요금은 다음 분기의 첫 달인 10월 말에 계좌로 지급된다.&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;신청장소: 성남시 농협, 축협&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;문의: 성남시 대중교통과 (031-3711-3715) 혹은 성남시콜센터 (1577-3100)&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;벌써 성남시 내에 전체 대상자 중에 40% 이상이 지원했다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;늦지 말고 지원해서 혜택을 얻기를 바란다.&lt;/p&gt;</description>
      <category>일상Tip</category>
      <category>성남시 어르신 버스요금 #성남시 어르신 버스요금 지원사업 #성남시민 만 70세 이상 버스요금 #어르신 버스요금 지원 방법 #버스요금 지원</category>
      <author>comet observer</author>
      <guid isPermaLink="true">https://comet-info.tistory.com/15</guid>
      <comments>https://comet-info.tistory.com/15#entry15comment</comments>
      <pubDate>Tue, 27 Jun 2023 22:11:58 +0900</pubDate>
    </item>
    <item>
      <title>만 나이 적용 시행으로 무엇이 달라질까?</title>
      <link>https://comet-info.tistory.com/14</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;6월 28일 드디어 '만 나이 통일법'이 시행된다.&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;제목을 입력해주세요_-001.jpg&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cVqt3F/btsljg8N4Oj/T9ykgPq2rD8um0C5m0qtt1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cVqt3F/btsljg8N4Oj/T9ykgPq2rD8um0C5m0qtt1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cVqt3F/btsljg8N4Oj/T9ykgPq2rD8um0C5m0qtt1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcVqt3F%2Fbtsljg8N4Oj%2FT9ykgPq2rD8um0C5m0qtt1%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;533&quot; height=&quot;533&quot; data-filename=&quot;제목을 입력해주세요_-001.jpg&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 기존에 사용해왔던 '만 나이'의 개념에 대해서 다시 한번 짚고 넘어가자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'만 나이'는 출생일을 기준으로 1년이 지났을 때 한 살이 되는 것으로 국제적으로 통용되는 나이 계산법이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그동안 대한민국은 일상에서 사용하는 '세는 나이'와 '만 나이'를 통일하지 않고 사용해왔다. 물론 법률적으로나 계약상에서는 만 나이를 주로 사용해 왔지만, 일상에서 사용하고 있는 나이와 민법이 규정하고 있는 법적 나이가 일치하지 않아, 발생하는 혼선과 법적 다툼이 발생하기도 했었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단한 만 나이 계산법은 다음과 같다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;올해 생일이 지난 경우라면 현재 연도에서 출생연도를 빼면 된다.&lt;/li&gt;
&lt;li&gt;올해 생일이 지나지 않은 경우에는 현재 연도에서 출생연도를 뺀 후 1을 한 번 더 빼준다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직접 계산하기가 귀찮다면 네이버 만 나이 계산기를 이용해보자. -&amp;gt; &lt;a style=&quot;background-color: #e6f5ff; color: #0070d1; text-align: start;&quot; href=&quot;https://search.naver.com/search.naver?where=nexearch&amp;amp;sm=top_hty&amp;amp;fbm=0&amp;amp;ie=utf8&amp;amp;query=%EB%A7%8C+%EB%82%98%EC%9D%B4+%EA%B3%84%EC%82%B0%EA%B8%B0&quot;&gt;네이버 만 나이 계산기&lt;/a&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;금융 이용에서 달라지는 것들&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;은행이나 카드사, 보험 회사 등은 이미 대부분 만 나이를 적용한 기준으로 시스템을 운용하고 있어서 크게 달라질 것은 없다. 다만, 몇몇 상품이나 이벤트에 대한 나이 조건에 대해 점검하고 있다. 또한 보험사의 경우는 나이 기준이 무엇보다 중요하기 때문에 이번 기회에 다시 한번 상품 개별 약관을 점검해 보는것도 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;연금 수령이나 그 외에 달라지는 것들&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;국민연금 또한 수령 나이 가 만 나이 기준으로 되어있기 때문에 달라지는 점은 없다고 봐도 무방하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 외에 정년이나 교통비 지원 연령 등도 이미 만 나이로 기록되어 있어서 달라지는 점은 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;만 나이가 적용되지 않는 법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대부분의 법령이 만 나이를 기준으로 제정되지만, '연 나이' 규정의 법령도 제법 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;청소년 보호법&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 현재 연 19세 미만의 청소년을 규정하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 만 나이를 적용하면 음주, 흡연 등의 규정이 생일에 따라 각자 다르게 적용되기 때문에 행정상 혼란이 올 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 따라서, 현재 '연 나이' 법령을 유지한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;병역법, 시험 응시 나이와 교육 관련 법령 또한 청소년 보호법과 비슷한 이유로 원활한 행정을 위해 '연 나이'를 사용하는 것을 유지한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 만 나이가 2023년 6월 28일에 공식적으로 시행되면서 우리 삶에서 변화되는 부분이 있을지 살펴보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생각보다 많은 부분이 이미 만 나이로 되어있어서, 크게 체감상 변화를 느낄만한 것은 없어보인다. 이제 일상에서 사람들과 자연스럽게 '만 나이'로 나이를 이야기 하게 되는 날이 올 수 있을지 사회 변화를 지켜보는 것도 재미있을 듯 하다.&lt;/p&gt;</description>
      <category>일상Tip</category>
      <category>만 나이 변화 #만 나이 계산기 #만 나이 은행 #만 나이 보험 #만 나이 연금</category>
      <author>comet observer</author>
      <guid isPermaLink="true">https://comet-info.tistory.com/14</guid>
      <comments>https://comet-info.tistory.com/14#entry14comment</comments>
      <pubDate>Sun, 25 Jun 2023 14:44:10 +0900</pubDate>
    </item>
    <item>
      <title>최적화는 신중히 하기</title>
      <link>https://comet-info.tistory.com/13</link>
      <description>&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;아주 예전의 프로그래머들의 최적화에 대한 이야기를 들어보면, 다음과 같다. &quot;효율성이라는 이름 아래 행해진 컴퓨틴 죄악이 더 많다&quot;, 자그마한 효율성은 모두 잊자. 섣부른 최적화가 만악의 근원&quot;, &quot;최적화를 할 때는 다음 규칙을 따르자. 첫 번째는 하지 마라이고 두 번째는 명백하고 최적화되지 않은 해법을 찾을 때까지는 하지 마라&quot; 위와 같은 이야기들은 최적화의 어두운 진실을 이야기해 준다. 최적화는 좋은 결과보다는 해로운 결과로 이어지기 쉽고, 섣불리 진행하면 특히 더 그렇게 된다. 빠르지도 않고 제대로 동작하지도 않으면서 수정하기는 어려운 소프트웨어를 탄생시키는 것이다. 성능 때문에 견고한 구조를 희생하지 말자. 빠른 프로그램보다는 좋은 프로그램을 작성하자. 좋은 프로그램이지만 원하는 성능이 나오지 않는다면 그 아키텍처 자체가 최적화할 수 있는 길을 안내해 줄 것이다. 좋은 프로그램은 정보 은닉 원칙을 따르므로 개별 구성요소의 내부를 독립적으로 설계할 수 있다. 따라서 시스템의 나머지에 영향을 주지 않고도 각 요소를 다시 설계할 수 있다. 프로그램을 완성할 때까지 성능 문제를 무시하라는 뜻이 아니다. 구현상의 문제는 나중에 최적화해 해결할 수 있지만, 아키텍처의 결함이 성능을 제한하는 상황이라면 시스템 전체를 다시 작성하지 않고는 해결하기 불가능할 수 있다. 완성된 설계의 기본 틀을 변경하려다 보면 유지보수하거나 개선하기 어려운 꼬인 구조의 시스템이 만들어지기 쉽기 때문이다. 따라서 설계 단계에서 성능을 반드시 염두에 두어야 한다. 성능을 제한하는 설계를 피하라. 완성 후 변경하기가 가장 어려운 설계 요소는 바로 컴포넌트끼리, 혹은 외부 시스템과의 소통 방식이다. API 네트워크 프로토콜, 영구 저장용 데이터 포맷 등이 대표적이다. 이런 설계 요소들은 완성 후에는 변경하기 어렵거나 불가능할 수 있으며, 동시에 시스템 성능을 심각하게 제한할 수 있다. API를 설계할 때 성능에 주는 영향을 고려하라. public 타입을 가변으로 만들면, 즉 내부 데이터를 변경할 수 있게 만들면 불필요한 방어적 복사를 수없이 유발할 수 있다. 비슷하게, 컴포지션으로 해결할 수 있음에도 상속 방식으로 설계한 public 클래스는 상위 클래스에 영원히 종속되며 그 성능 제약까지도 물려받게 된다. 인터페이스도 있는데 굳이 구현 타입을 사용하는 것 역시 좋지 않다. 특정 구현체에 종속되게 하여, 나중에 더 빠른 구현체가 나오더라도 이용하지 못하게 된다. API 설계가 성능에 주는 영향은 현실적인 문제다. 
&lt;script async src=&quot;https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-6269565343097644&quot;
     crossorigin=&quot;anonymous&quot;&gt;&lt;/script&gt;
&lt;!-- 디스플레이 광고 반응형 --&gt;
&lt;ins class=&quot;adsbygoogle&quot;
     style=&quot;display:block&quot;
     data-ad-client=&quot;ca-pub-6269565343097644&quot;
     data-ad-slot=&quot;9993645136&quot;
     data-ad-format=&quot;auto&quot;
     data-full-width-responsive=&quot;true&quot;&gt;&lt;/ins&gt;
&lt;script&gt;
     (adsbygoogle = window.adsbygoogle || []).push({});
&lt;/script&gt;
  java.awt.Component 클래스의 getSize 메서드를 생각해 보자. 이 API 설계자는 이 메서드가 Dimension 인터페이스를 반환하도록 결정했다. 여기에 더해 Dimension은 가변으로 설계했으니 getSize를 호출하는 모든 곳에서 Dimension 인스턴스를 새로 생성해야만 한다. 요즘 VM이라면 이런 작은 객체를 몇 개 생성하는 게 큰 부담이 아니지만, 수백만 개를 생성해야 한다면 이야기가 달라진다. 이 API를 다르게 설계했을 수도 있다. Dimension을 불변으로 만드는 게 가장 이상적이지만, getSize를 getWidth와 getHeight로 나누는 방법도 있다. 즉, Dimension 객체의 기본 타입 값들을 따로따로 반환하는 방식이다. 실제로도 자바 2에서는 성능 문제를 해결하고자 Component 클래스에 이 메서드들을 추가했다. 하지만 기존 클라이언트 코드는 여전히 getSize 메서드를 호출하며 원래 내렸던 API 설계 결정의 폐해를 감내하고 있다. 다행히 잘 설계된 API는 성능도 좋은 게 보통이다. 그러니 성능을 위해 API를 왜곡하는 건 매우 안 좋은 생각이다. API를 왜곡하도록 만든 그 성능 문제는 해당 플랫폼이나 아랫단 소프트웨어의 다음 버전에서 사라질 수도 있지만, 왜곡된 API와 이를 지원하는 데 따르는 고통은 영원히 계속될 것이다. 신중하게 설계하여 깨끗하고 명확하고 멋진 구조를 갖춘 프로그램을 완성한 다음에야 최적화를 고려해 볼 차례가 된다. 물론 성능에 만족하지 못할 경우에 한정된다. 최적화 규칙에 한 가지를 추가해 보자. &quot;각각의 최적화 시도 전후로 성능을 측정하라&quot; 정도가 되겠다. 아마도 측정 결과에 놀랄 때가 많을 것이다. 시도한 최적화 기법이 성능을 눈에 띄게 높이지 못하는 경우가 많고, 심지어 더 나빠지게 할 때도 있다. 주요 원인은 프로그램에서 시간을 잡아먹는 부분을 추측하기가 어렵기 때문이다. 느릴 거라고 짐작한 부분이 사실은 성능에 별다른 영향을 주지 않는 곳이라면 시간만 허비한 꼴이 된다. 일반적으로 90%의 시간을 단 10%의 코드에서 사용한다는 사실을 기억해 두자. 프로파일링 도구는 최적화 노력을 어디에 집중해야 할지 찾는 데 도움을 준다. 이런 도구는 개별 메서드의 소비 시간과 호출 횟수 같은 런타임 정보를 제공하여, 집중할 곳은 물론 알고리즘을 변경해야 한다는 사실을 알려주기도 한다. 프로그램에 시간이 거듭제곱으로 증가하는 알고리즘이 숨어 있다면 더 효율적인 것으로 교체해야 한다. 그러면 다른 튜닝을 하지 않아도 문제가 사라질 것이다. 시스템 규모가 커질수록 프로파일러가 더 중요해진다. 건초더미에서 바늘 찾기와 비슷하다. 건초더미가 거대해질수록 금속탐지기가 더 절실해진다. 그 외에 jmh도 언급해 둘 만한 도구이다. 프로파일러는 아니지만 자바 코드의 상세한 성능을 알기 쉽게 보여주는 마이크로 벤치마킹 프레임워크다. 최적화 시도 전후의 성능 측정은 C와 C++ 같은 전통적인 언어에서도 중요하지만, 성능 모델이 덜 정교한 자바에서는 중요성이 더욱 크다. 자바는 다양한 기본 연산에 드는 상대적인 비용을 덜 명확하게 정의하고 있다. 다시 말해, 프로그래머가 작성하는 코드와 CPU에서 수행하는 명령 사이의 추상화 격차가 커서 최적화로 인한 성능 변화를 일정하게 예측하기가 그만큼 더 어렵다. 그래서인지 최적화가 관련해 일부만 맞거나 터무니없는 미신들이 떠돌아다닌다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;요약하자면,&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빠른 프로그램을 작성하려 안달 내지 말자. 좋은 프로그램을 작성하다 보면 성능은 따라오게 마련이다. 하지만 시스템을 설계할 때, 특히 API, 네트워크 프로토콜, 영구 저장용 데이터 포맷을 설계할 때는 성능을 염두에 두어야 한다. 시스템 구현을 완료했다면 이제 성능을 측정해 보라. 충분히 빠르면 그것으로 끝이다. 그렇지 않다면 프로파일러를 사용해 문제의 원인이 되는 지점을 찾아 최적화를 수행하자. 가장 먼저 어떤 알고리즘을 사용했는지를 살펴보자. 알고리즘을 잘못 골랐다면 다른 저수준 최적화는 아무리 해봐야 소용이 없다. 만족할 때까지 이 과정을 반복하고, 모든 변경 후에는 성능을 측정하자.&lt;/p&gt;</description>
      <category>IT 개발/Effective Java</category>
      <author>comet observer</author>
      <guid isPermaLink="true">https://comet-info.tistory.com/13</guid>
      <comments>https://comet-info.tistory.com/13#entry13comment</comments>
      <pubDate>Wed, 1 Mar 2023 11:00:19 +0900</pubDate>
    </item>
    <item>
      <title>일반적으로 통용되는 명명 규칙을 따르기</title>
      <link>https://comet-info.tistory.com/12</link>
      <description>&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;자바의 명명 규칙은 자바 언어 명세에 잘 기술되어 있다. 크게 철자와 문법, 두 범주로 나뉜다. 철자 규칙은 패키지, 클래스, 인터페이스, 메서드, 필드, 타입 변수의 이름을 다룬다. 이 규칙들은 특별한 이유가 없는 한 반드시 따라야 한다. 이 규칙을 어긴 API는 사용하기 어렵고, 유지보수하기 어렵다. 철자 규칙이나 문법 규칙을 어기면 다른 프로그래머들이 그 코드를 읽기 번거로울 뿐 아니라 다른 뜻으로 오해할 수도 있고 그로 인해 오류까지 발생할 수 있다. 패키지와 모듈 이름은 각 요소를 점으로 구분하여 계층적으로 짓는다. 요소들은 모두 소문자 알파벳 혹은 숫자로 이뤄진다. 내부조직이 아닌 바깥에서도 사용될 패키지라면 조직의 인터넷 도메인 이름을 역순으로 사용한다. 예외적으로 표준 라이브러리와 선택적 패키지들은 각각 java와 javax로 시작한다. 도메인 이름을 패키지 이름의 접두어로 변환하는 자세한 규칙은 자바 언어 명세에 적혀 있다. 패키지 이름의 나머지는 해당 패키지를 설명하는 하나 이상의 요소로 이뤄진다. 각 요소는 일반적으로 8자 이하의 짧은 단어로 한다. utiliites보다는 util처럼 의미가 통하는 약어를 추천한다. 여러 단어로 구성된 이름이라면 awt처럼 각 단어의 첫 글자만 따서 써도 좋다. 요소의 이름은 보통 한 단어 혹은 약어로 이뤄진다. 인터넷 도메인 이름 뒤에 요소 하나만 붙인 패키지가 많지만, 많은 기능을 제공하는 경우엔 계층을 나눠 더 많은 요소로 구성해도 좋다. 예를 들어, java.util은 java.util.concurrent.atomic과 같이 그 밑에 수많은 패키지를 가지고 있다. 자바가 패키지 계층에 관해 언어 차원에서 지원하는 건 거의 없지만, 어쨌든 이처럼 하부의 패키지를 하위 패키지라 부른다. 클래스와 인터페이스의 이름은 하나 이상의 단어로 이뤄지며, 각 단어는 대문자로 시작한다. 여러 단어의 첫 글자만 딴 약자나 max, min처럼 널리 통용되는 줄임말을 제외하고는 단어를 줄여 쓰지 않도록 한다. 약자의 경우 첫 글자만 대문자로 할지 전체를 대문자로 할지는 살짝 논란이 있다. 전체를 대문자로 쓰는 프로그래머도 있지만, 그래도 첫 글자만 대문자로 하는 쪽이 훨씬 많다. HttpUrl처럼 여러 약자가 혼합된 경우라도 각 약자의 시작과 끝을 명확히 알 수 있기 때문이다. 메서드와 필드 이름은 첫 글자를 소문자로 쓴다는 점만 빼면 클래스 명명 규칙과 같다. 첫 단어가 약자라면 단어 전체가 소문자여야 한다. 단 상수 필드는 예외다. 상수 필드를 구성하는 단어는 모두 대문자로 쓰며 단어 사이는 밑줄로 구분한다. 상수 필드는 값이 불변인 static final 필드를 말한다. 달리 말하면 static final 필드의 타입이 기본 타입이나 불변 참조 타입이라면 상수 필드에 해당한다. static final 필드이면서 가리키는 객체가 불변이라면 비록 그 타입은 가변이라도 상수 필드다. 이름에 밑줄을 사용하는 요소로는 상수 필드가 유일하다는 사실도 기억해두자. 지역변수에도 다른 멤버와 비슷한 명명 규칙이 적용된다. 단, 약어를 써도 좋다. 약어를 써도 그 변수가 사용되는 문맥에서 의미를 쉽게 유추할 수 있기 떄문이다. 입력 매개변수도 지역변수의 하나다. 하지만 메서드 설명 문서에까지 등장하는 만큼 일반 지역변수보다는 신경을 써야 한다. 타입 매개변수 이름은 보통 한 문자로 표현한다. 대부분은 다음의 다섯 가지 중 하나다. 임의의 타입엔 T를, 컬렉션 원소의 타입은 E를, 맵의 키와 값에는 K와 V를 예외에는 X를 메서드의 반환 타입에는 R을 사용한다. 그 외에 임의 타입의 시퀀스에는 T, U, V 혹은 T1, T2, T3를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문법 규칙은 철자 규칙과 비교하면 더 유연하고 논란도 많다. 패키지에 대한 규칙은 따로 없다. 객체를 생성할 수 있는 클래스의 이름은 보통 단수 명사나 명사구를 사용한다. 객체를 생성할 수 없는 클래스의 이름은 보통 복수형 명사로 짓는다. 인터페이스 이름은 클래스와 똑같이 짓거나 able 혹은 ible로 끝나는 형용사로 짓는다. 애너테이션은 워낙 다양하게 활용되어 지배적인 규칙이 없이 명사, 동사, 전치사, 형용사가 두루 쓰인다. 어떤 동작을 수행하는 메서드의 이름은 동사나 동사구로 짓는다. boolean 값을 반환하는 메서드라면 보통 is나 has로 시작하고 명사나 명사구, 혹은 형용사로 기능하는 아무 단어나 구로 끝나도록 짓는다. 반환 타입이 boolean이 아니거나 해당 인스턴스의 속성을 반환하는 메서드의 이름은 보통 명사, 명사구, 혹은 get으로 시작하는 동사구로 짓는다. 세 번째 형식, 즉 get으로 시작하는 형태만 써야 한다는 주장도 있지만, 근거가 빈약하다. 보통은 처음 두 형태를 사용한 코드의 가독성이 더 좋기 때문이다. get으로 시작하는 형태는 주로 자바빈즈 명세에 뿌리를 두고 있다. 자바빈즈는 재사용을 위한 컴포넌트 아키텍처의 초기 버전 중 하나로, 최근의 도구 중에도 이 명명 규칙을 따르는 경우가 제법 많다. 따라서 이런 도구와 어우러지는 코드를 작성한다면 이 규칙을 따라도 상관없다. 한편 클래스가 한 속성의 게터와 세터를 모두 제공할 때도 적합한 규칙이다. 이런 경우라면 보통 getAttribute와 setAttribute 형태의 이름을 갖게 될 것이다. 꼭 언급해둬야 할 특별한 메서드 이름이 몇 가지 있다. 객체의 타입을 바꿔서, 다른 타입의 또 다른 객체를 반환하는 인스턴스 메서드의 이름은 보통 toType 형태로 짓는다. 객체의 내용을 다른 뷰로 보여주는 메서드의 이름은 asType 형태로 짓는다. 객체의 값을 기본 타입 값으로 반환하는 메서드의 이름은 보통 typeValue 형태로 짓는다. 마지막으로, 정적 팩터리의 이름은 다양하지만 from, of, valueOf, instance, getInstance, newInstance, getType, newType을 흔히 사용한다. 필드 이름에 관한 문법 규칙은 클래스, 인터페이스, 메서드 이름에 비해 덜 명확하고 덜 중요하다. API 설계를 잘 했다면 필드가 직접 노출될 일이 거의 없기 때문이다. boolean 타입의 필드 이름은 보통 boolean 접근자 메서드에서 앞 단어를 뺀 형태다. 다른 타입의 필드라면 명사나 명사구를 사용한다. 지역변수 이름도 필드와 비슷하게 지으면 되나, 조금 더 느슨하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;요약하자면,&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;표준 명명 규칙을 체화하여 자연스럽게 베어 나오도록 하자. 철자 규칙은 직관적이라 모호한 부분이 적은데 반해, 문법 규칙은 더 복잡하고 느슨하다. 자바 언어 명세의 말을 인용하자면 오랫동안 따라온 규칙과 충돌한다면 그 규칙을 맹종해서는 안된다라고 한다. 상식이 이끄는 대로 따르자.&lt;/p&gt;</description>
      <category>IT 개발/Effective Java</category>
      <author>comet observer</author>
      <guid isPermaLink="true">https://comet-info.tistory.com/12</guid>
      <comments>https://comet-info.tistory.com/12#entry12comment</comments>
      <pubDate>Sat, 25 Feb 2023 15:29:00 +0900</pubDate>
    </item>
    <item>
      <title>자바에서 문자열 사용과 연결 이야기</title>
      <link>https://comet-info.tistory.com/11</link>
      <description>&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;문자열은 다른 값 타입을 대신하기에 적합하지 않다. 많은 사람이 파일, 네트워크, 키보드 입력으로부터 데이터를 받을 때 주로 문자열을 사용한다. 자연스러워 보이지만, 입력받을 데이터가 진짜 문자열일 때만 그렇게 하는 게 좋다. 받은 데이터가 수치형이라면 int, float, BigInteger 등 적당한 수치 타입으로 변환해야 한다. 예나 아니요 같은 질문의 답이라면 적절한 열거 타입이나 boolean으로 변환해야 한다. 일반화해 이야기하자면 기본 타입이든 참조 타입이든 적절한 값 타입이 있다면 그것을 사용하고 없다면 새로 하나 작성하자. 의외로 지켜지지 않는 경우가 많다. 문자열은 열거 타입을 대신하기에 적합하지 않다. 상수를 열거할 때는 문자열보다는 열거 타입이 월등히 낫다. 문자열은 혼합 타입을 대신하기에 적합하지 않다. 여러 요소가 혼합된 데이터를 하나의 문자열로 표현하는 것은 대체로 좋지 않은 생각이다. 예를 들어 &quot;String compoundKey = className + &quot;#&quot; + i.next();&quot; 와 같은 방식은 단점이 많은 방식이다. 혹여라도 두 요소를 구분해주는 문자 #이 두 요소 중 하나에서 쓰였다면 혼란스러운 결과를 초래한다. 각 요소를 개별로 접근하려면 문자열을 파싱해야 해서 느리고, 귀찮고, 오류 가능성도 커진다. 적절한 equals, toString, compareTo 메서드를 제공할 수 없으며, String이 제공하는 기능에만 의존해야 한다. 그래서 차라리 전용 클래스를 새로 만드는 편이 낫다. 이런 클래스는 보통 private 정적 멤버 클래스로 선언한다. 문자열은 권한을 표현하기에 적합하지 않다. 권한을 문자열로 표현하는 경우가 종종 있다. 예를 들어 스레드 지역변수 기능을 설계한다고 해보자. 그 이름처럼 각 스레드가 자신만의 변수를 갖게 해주는 기능이다. 자바가 이 기능을 지원하기 시작한 때는 자바 2부터로, 그전에는 프로그래머가 직접 구현해야 했다. 그 당시 이 기능을 설계해야 했던 여러 프로그래머가 독립적으로 방법을 모색하다가 종국에는 똑같은 설계에 이르렀다. 바로 클라이언트가 제공한 문자열 키로 스레드별 지역변수를 식별한 것이다. 이 방식의 문제는 스레드 구분용 문자열 키가 전역 이름공간에서 공유된다는 점이다. 이 방식이 의도대로 동작하려면 각 클라이언트가 고유한 키를 제공해야 한다. 그런데 만약 두 클라이언트가 서로 소통하지 못해 같은 키를 쓰기로 결정한다면, 의도치 않게 같은 변수를 공유하게 된다. 결국 두 클라이언트 모두 제대로 기능하지 못할 것이다. 보안도 취약하다. 악의적인 클라이언트라면 의도적으로 같은 키를 사용하여 다른 클라이언트의 값을 가져올 수도 있다. 이 API는 문자열 대신 위조할 수 없는 키를 사용하면 해결된다. 이 키를 권한이라고도 한다. 이렇게 키를 사용하는 방법은 문자열 기반 API의 문제 두 가지를 모두 해결해주지만, 개선할 여지가 있다. 고민해보자.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문자열에 대한 또 다른 이야기를 해보자. 자주 사용하는 문자열 연산에 관한 이야기이다. 문자열 연결 연산자(+)는 여러 문자열을 하나로 합쳐주는 편리한 수단이다. 그런데 한 줄짜리 출력값 혹은 작고 크기가 고정된 객체의 문자열 표현을 만들 때라면 괜찮지만, 본격적으로 사용하기 시작하면 성능 저하를 감내하기 어렵다. 문자열 연결 연산자로 문자열 n개를 잇는 시간은 n 제곱에 비례한다. 문자열은 불변이라서 두 문자열을 연결할 경우 양쪽의 내용을 모두 복사해야 하므로 성능 저하는 피할 수 없는 결과다. 예를 들어 다음 메서드는 청구서의 품목을 전부 하나의 문자열로 연결해준다.&lt;/p&gt;
&lt;pre id=&quot;code_1677157604372&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public String statement() {
	String result = &quot;&quot;;
	for (int i = 0; i &amp;lt; numItems(); i++)
		result += lineForItem(i);
	return result;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;품목이 많을 경우 이 메서드는 심각하게 느려질 수 있다. 성능을 포기하고 싶지 않다면 String 대신 StringBuilder를 사용하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1677158240300&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public String statementWithStringBuilder() {
	StringBuilder sb = new StringBuilder(numItems() * LINE_WIDTH);
	for (int i = 0; i &amp;lt; numItems(); i++)
		sb.append(lineForItem(i));
	return sb.toString();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바 6 이후 문자열 연결 성능을 다방면으로 개선했지만, 이 두 메서드의 성능 차이는 여전히 크다. 품목을 100개로 하고 lineForItem이 길이 80인 문자열을 반환하게 하여 테스트해보니 StringBuilder를 사용했을 때가 6.5배나 빨랐다. statement 메서드의 수행 시간은 품목 수의 제곱이 비례해 늘어나고 StringBuilder를 사용했을 경우는 선형으로 늘어나므로, 품목 수가 늘어날수록 성능 격차도 점점 벌어질 것이다. StringBuilder를 사용한 예제에서 StringBuilder를 선언할 때, 전체 결과를 담기에 충분한 크기로 초기화한 점을 잊지 말자. 하지만 기본값을 사용하더라도 여전히 5.5배나 빠르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;요약하자면,&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 적합한 데이터 타입이 있거나 새로 작성할 수 있다면, 문자열을 쓰고 싶은 유혹을 뿌리치자. 또한 문자열 연결에 대한 원칙은 간단하다. 성능에 신경 써야 한다면 많은 문자열을 연결할 때는 문자열 연결 연산자(+)를 피하자. 대신 StringBuilder의 append 메서드를 사용하라. 문자 배열을 사용하거나 문자열을 연결하지 않고 하나씩 처리하는 방법도 있다.&lt;/p&gt;</description>
      <category>IT 개발/Effective Java</category>
      <author>comet observer</author>
      <guid isPermaLink="true">https://comet-info.tistory.com/11</guid>
      <comments>https://comet-info.tistory.com/11#entry11comment</comments>
      <pubDate>Fri, 24 Feb 2023 23:18:12 +0900</pubDate>
    </item>
    <item>
      <title>박싱된 기본타입보다는 기본 타입을 사용하기</title>
      <link>https://comet-info.tistory.com/10</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;자바를 사용하면서 primitive타입과 primitive타입에 대응하는 래핑된 object 타입 중 어떤 것을 선택할지는 간단해 보이지만, 중요한 문제이다. 기본적으로 성능을 생각하면 heap 영역에 영향이 없는 primitive 타입을 선택하지만, 어쩔 수 없이 (없는 값의 기본을 null로 해야 명세에 부합하여 사이드 이펙트가 없을 때) 래퍼 클래스를 사용하는 경우도 있었다. 이번에는 이 부분에 대해 더 자세히 알아보자.&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;자바에서는 int, double, boolean과 같은 기본 타입에 대응하는 박싱된 기본 타입 (Integer, Double, Boolean 등) 들이 있다. 오토박싱과 오토언박싱 덕분에 두 타입을 크게 구분하지 않고 사용할 수는 있지만, 차이가 사라지는 것은 아니다. 둘 사이에는 분명한 차이가 있으니 어떤 타입을 사용하는지는 상당히 중요하다. 주의해서 선택해야 한다는 말이다. 기본 타입과 박싱된 기본 타입의 주된 차이는 크게 세 가지다. 첫 번째, 기본 타입은 값만 가지고 있으나, 박싱된 기본 타입은 값에 더해 식별성이란 속성을 갖는다. 달리 말하면 박싱된 기본 타입의 두 인스턴스는 값이 같아도 서로 다르다고 식별될 수 있다. 두 번째, 기본 타입의 값은 언제나 유효하나, 박싱된 기본 타입은 유효하지 않은 값, 즉 null을 가질 수 있다. 세 번째, 기본 타입이 박싱된 기본 타입보다 시간과 메모리 사용 면에서 더 효율적이다. 이상의 세 가지 차이 때문에 주의하지 않고 사용하면 진짜로 문제가 발생할 수 있다. Integer값을 오름차순으로 정렬하는 비교자를 보자. Integer는 그 자체로 순서가 있으니 이 비교자가 실질적인 의미는 없지만, 흥미로운 점을 하나 보여준다.&lt;/p&gt;
&lt;pre id=&quot;code_1677155180824&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Comparator&amp;lt;Integer&amp;gt; naturalOrder = (i, j) -&amp;gt; (i &amp;lt; j) ? -1 : (i == j ? 0 : 1);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;별다른 문제를 찾기 어렵고, 실제로 이것저것 테스트해봐도 잘 통과한다. 예컨대 Collections.sort에 원소 백만 개짜리 리스트와 이 비교자를 넣어 돌려도 아무 문제가 없다. 리스트에 중복이 있어도 상관없다. 하지만 심각한 결함이 숨어 있으니 이 결함을 눈으로 확인하고 싶다면 naturalOrder.compare(new Integer(42), new Integer(42))의 값을 출력해보자. 두 Integer 인스턴스의 값이 42로 같으므로 0을 출력해야 하지만, 실제로는 1을 출력한다. 즉, 첫 번째 Integer가 두 번째보다 크다고 주장한다. 원인이 뭘까? naturalOrder의 첫 번째 검사(i &amp;lt;j)는 잘 작동한다. 여기서 i와 j가 참조하는 오토박싱된 Integer 인스턴스는 기본 타입 값으로 변환된다. 그런 다음 첫 번째 정숫값이 두 번째 값보다 작은지를 평가한다. 만약 작지 않다면 두 번째 검사(i == j)가 이뤄진다. 그런데 이 두 번째 검사에서는 두 객체 참조의 식별성을 검사하게 된다. 비록 값은 같더라도 i와 j가 서로 다른 Integer 인스턴스라면 이 비교의 결과는 false가 되고, 비교자는 1을 반환한다. 즉, 첫 번째 Integer 값이 두 번째보다 크다는 것이다. 이처럼 박싱된 기본 타입에 == 연산자를 사용하면 오류가 일어난다. 실무에서 이와 같이 기본 타입을 다루는 비교자가 필요하다면 Comparator.naturalOrder()를 사용하자. 비교자를 직접 만들면 비교자 생성 메서드나 기본 타입을 받는 정적 compare 메서드를 사용해야 한다. 그렇더라도 이 문제를 고치려면 지역변수 2개를 두어 각각 박싱된 Integer 매개변수의 값을 기본 타입 정수로 저장한 다음, 모든 비교를 이 기본 타입 변수로 수행해야 한다. 이렇게 하면 오류의 원인인 식별성 검사가 이뤄지지 않는다.&lt;/p&gt;
&lt;pre id=&quot;code_1677155548388&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Comparator&amp;lt;Integer&amp;gt; naturalOrder = (iBoxed, jBoxed) -&amp;gt; {
	int i = iBoxed, j = jBoxed;
	return i &amp;lt; j ? -1 : (i == j ? 0 : 1);
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Integer와 int를 비교할때를 보면, 거의 예외 없이 기본 타입과 박싱된 기본 타입을 혼용한 연산에서는 박싱된 기본 타입의 박싱이 자동으로 풀린다. 그리고 null 참조를 언박싱하면 NullPointerException이 발생한다. 이 예에서 보듯, 이런 일은 어디서든 벌어 질 수 있다. 해법은 i를 int로 바꿔주면 된다. 또한, 실수로 for문을 돌면서 더하는 연산을 할때, sum의 값을 박싱된 기본 타입으로 선언하면 박싱과 언박싱이 반복해서 일어나기 때문에, 치명적으로 느려지는것을 체감할 수 있다. 모든 문제의 원인은 하나다. 프로그래머가 기본 타입과 박싱된 기본 타입의 차이를 무시한 대가를 치른 것이다. 그렇다면 박싱된 기본 타입은 언제 써야 하는가? 적절히 쓰이는 경우가 몇 가지 있다. 첫 번째, 컬렉션의 원소, 키, 값으로 쓴다. 컬렉션은 기본 타입을 담을 수 없으므로 어쩔 수 없이 박싱된 기본 타입을 써야만 한다. 더 일반화해 말하면, 매개변수화 타입이나 매개변수화 메서드의 타입 매개변수로는 박싱된 기본 타입을 써야 한다. 자바 언어가 타입 매개변수로 기본 타입을 지원하지 않기 때문에다. 예컨대 변수를 ThreadLocal&amp;lt;int&amp;gt; 타입으로 선언하는 건 불가능하며, 대신 ThreadLocal&amp;lt;Integer&amp;gt;를 써야 한다. 마지막으로, 리플렉션을 통해 메서드를 호출할 때도 박싱된 기본 타입을 사용해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;요약하자면,&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 타입과 박싱된 기본 타입 중 하나를 선택해야 한다면 가능하면 기본 타입을 사용하라. 기본 타입은 간단하고 빠르다. 박싱된 기본 타입을 써야 한다면 주의를 기울이자. 오토박싱이 박싱된 기본 타입을 사용할 때의 번거로움을 줄여주지만, 그 위험까지 없애주지는 않는다. 두 박싱된 기본 타입을 == 연산자로 비교한다면 식별성 비교가 이뤄지는데, 이는 원한 게 아닐 가능성이 크다. 같은 연산에서 기본 타입과 박싱된 기본 타입을 혼용하면 언박싱이 이뤄지며, 언박싱 과정에서 NullPointerException을 던질 수 있다. 마지막으로, 기본 타입을 박싱하는 작업은 필요 없는 객체를 생성하는 부작용을 낳을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출처: 이펙티브 자바 3판&lt;/p&gt;</description>
      <category>IT 개발/Effective Java</category>
      <author>comet observer</author>
      <guid isPermaLink="true">https://comet-info.tistory.com/10</guid>
      <comments>https://comet-info.tistory.com/10#entry10comment</comments>
      <pubDate>Thu, 23 Feb 2023 21:46:14 +0900</pubDate>
    </item>
    <item>
      <title>메서드 시그니처를 신중히 설계하기</title>
      <link>https://comet-info.tistory.com/9</link>
      <description>&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;메서드 이름을 신중히 짓자. 항상 표준 명명 규칙을 따르자. 이해할 수 있고, 같은 패키지에 속한 다른 이름들과 일관되게 짓는 게 최우선 목표다. 그다음 목표는 개발자 커뮤니티에서 널리 받아들여지는 이름을 사용하는 것이다. 긴 이름은 피하자. 애매하면 자바 라이브러리의 API 가이드를 참조하자. 자바 라이브러리가 워낙 방대하다 보니 일관되지 않은 이름도 제법 많이 있지만, 대부분은 납득할 만한 수준이다. 편의 메서드를 너무 많이 만들지 말자. 모든 메서드는 각각 자신의 소임을 다해야 한다. 메서드가 너무 많은 클래스는 익히고, 사용하고, 문서화하고, 테스트하고, 유지보수하기 어렵다. 인터페이스도 마찬가지다. 메서드가 너무 많으면 이를 구현하는 사람과 사용하는 사람 모두를 고통스럽게 한다. 클래스나 인터페이스는 자신의 각 기능을 완벽히 수행하는 메서드로 제공해야 한다. 아주 자주 쓰일 경우에만 별도의 약칭 메서드를 두자. 확신이 서지 않으면 만들지 말자. 매개변수 목록은 짧게 유지하자. 최대 4개 이하가 좋다. 일단 4개가 넘어가면 매개변수를 전부 기억하기가 쉽지 않다. API에 이 제한을 넘는 메서드가 많다면 프로그래머들은 API 문서를 옆에 끼고 개발해야 할 것이다. IDE를 사용하면 수고를 많이 덜 수 있지만, 여전히 매개변수 수는 적은 쪽이 훨씬 낫다. 같은 타입의 매개변수 여러 개가 연달아 나오는 경우가 특히 위험하다. 사용자가 매개변수 순서를 기억하기 어려울뿐더러, 실수로 순서를 바꿔 입력해도 그대로 컴파일되고 실행된다. 단지 의도와 다르게 동작할 뿐이다. 과하게 긴 매개변수 목록을 짧게 줄여주는 기술 세 가지가 있다. 첫 번째, 여러 메서드로 쪼갠다. 쪼개진 메서드 각각은 원래 매개변수 목록의 부분집합을 받는다. 잘못하면 메서드가 너무 많아질 수 있지만, 공통점이 없는 기능들이 분리되고 기능을 원자적으로 쪼개 제공함으로서 오히려 메서드 수를 줄여주는 효과도 있다. java.util.List 인터페이스가 좋은 예다. 리스트에서 주어진 원소의 인덱스를 찾아야 하는데, 전체 리스트가 아니라 지정된 범위의 부분리스트에서의 인덱스를 찾는다고 해보자. 이 기능을 하나의 메서드로 구현하려면 부분리스트의 시작, 부분리스트의 끝, 찾을 원소까지 총 3개의 매개변수가 필요하다. 그런데 List는 그 대신 부분리스트를 반환하는 subList 메서드와 주어진 원소의 인덱스를 알려주는 indexOf 메서드를 별개로 제공한다. subList가 반환한 부분리스트 역시 완벽한 List이므로 두 메서드를 조합하면 원하는 목적을 이룰 수 있다. 결과적으로 강함과 유연함이 절묘하게 균형을 이룬 API가 만들어진 것이다. 매개변수 수를 줄여주는 기술 두 번째는 매개변수 여러 개를 묶어주는 클래스를 만드는 것이다. 이런 클래스는 정적 맴버 클래스로 둔다. 특히 잇따른 매개변수 몇 개를 독립된 하나의 개념으로 볼 수 있을 때 추천하는 기법이다. 예를 들어 카드게임을 클래스로 만든다고 해보자. 그러면 메서드를 호출할 때 카드의 숫자와 무늬를 뜻하는 두 매개변수를 항상 같은 순서로 전달할 것이다. 따라서 이 둘을 묶는 클래스를 만들어 하나의 매개변수로 주고받으면 API는 물론 클래스 내부 구현도 깔끔해질 것이다. 세 번째는 앞서의 두 기법을 혼합한 것으로, 객체 생성에 사용한 빌더 패턴을 메서드 호출에 응용한다고 보면 된다. 이 기법은 매개변수가 많을 때, 특히 그 중 일부는 생략해도 괜찮을 때 도움이 된다. 먼저 모든 매개변수를 하나로 추상화한 객체를 정의하고, 클라이언트에서 이 객체의 세터 메서드를 호출해 필요한 값을 설정하게 하는 것이다. 이때 각 세터 메서드는 매개변수 하나 혹은 서로 연관된 몇 개만 설정하게 한다. 클라이언트는 먼저 필요한 매개변수를 다 설정한 다음, execute 메서드를 호출해 앞서 설정한 매개변수들의 유효성을 검사한다. 마지막으로, 설정이 완료된 객체를 넘겨 원하는 계산을 수행한다. 매개변수의 타입으로는 클래스보다는 인터페이스가 더 낫다. 매개변수로 적합한 인터페이스가 있다면 그 인터페이스를 직접 사용하자. 예를 들어 메서드에 HashMap을 넘길 일은 전혀 없다. 대신 Map을 사용하자. 그러면 HashMap뿐 아니라 TreeMap, ConcurrentHashMap, TreeMap의 부분맵 등 어떤 Map 구현체도 인수로 건넬 수 있다. 심지어 아직 존재하지 않는 Map도 가능하다. 인터페이스 대신 클래스를 사용하면 클라이언트에게 특정 구현체만 사용하도록 제한하는 꼴이며, 혹시라도 입력 데이터가 다른 형태로 존재한다면 명시한 특정 구현체의 객체로 옮겨 담느라 비싼 복사 비용을 치러야 한다. 또한 boolean보다는 원소 2개짜리 열거 타입이 낫다. 열거 타입을 사용하면 코드를 일고 쓰기가 더 쉬워진다. 나중에 선택지를 추가하기도 쉽다. 예를 들어 화씨온도와 섭씨온도를 원소로 정의한 열거 타입이 있을때, 온도계 클래스의 정적 팩터리 메서드가 이 열거 타입을 입력받아 적합한 온도계 인스턴스를 생성해준다고 해보자. 이럴때 확실히 열거 타입을 명시하는게 객체를 생성할때 하는 일을 명확히 알려준다. 또한, 온도 단위에 대한 의존성을 개별 열거 타입 상수의 메서드 안으로 리팩터링해 넣을 수도 있다. 예컨대 double 값을 받아 섭씨온도로 변환해주는 메서드를 열거 타입 상수 각각에 정의해둘 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;요약하자면,&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선순위를 따지자면, 같은 패키지에 속한 다른 이름들과 일관되게 표준 명명규칙을 따르는게 첫번째다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애매하면, 자바 라이브러리를 확인하거나 다른 유명한 오픈소스를 참조하자. 매개변수는 4개 이하로 만들자. 매개변수 타입은 인터페이스가 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출처: 이펙티브 자바 3판&lt;/p&gt;</description>
      <category>IT 개발/Effective Java</category>
      <author>comet observer</author>
      <guid isPermaLink="true">https://comet-info.tistory.com/9</guid>
      <comments>https://comet-info.tistory.com/9#entry9comment</comments>
      <pubDate>Wed, 22 Feb 2023 22:42:14 +0900</pubDate>
    </item>
    <item>
      <title>적시에 방어적 복사본 만들기</title>
      <link>https://comet-info.tistory.com/8</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;자바는 안전한 언어이긴 하지만, 더욱 안전하게 사용하기 위해 지켜야 할 것들이 많다. 이번 글은 자바를 더욱 안전하게 사용하기 위한 가이드이다.&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;자바는&amp;nbsp;C,&amp;nbsp;C++&amp;nbsp;같은&amp;nbsp;언어에서&amp;nbsp;흔히&amp;nbsp;보이는&amp;nbsp;버퍼&amp;nbsp;오버런,&amp;nbsp;배열&amp;nbsp;오버런,&amp;nbsp;와일드&amp;nbsp;포인터&amp;nbsp;같은&amp;nbsp;메모리&amp;nbsp;충돌&amp;nbsp;오류에서&amp;nbsp;안전하다.&amp;nbsp;자바로&amp;nbsp;작성한&amp;nbsp;클래스는&amp;nbsp;시스템의&amp;nbsp;다른&amp;nbsp;부분에서&amp;nbsp;무슨&amp;nbsp;짓을&amp;nbsp;하든&amp;nbsp;그&amp;nbsp;불변식이&amp;nbsp;지켜진다.&amp;nbsp;메모리&amp;nbsp;전체를&amp;nbsp;하나의&amp;nbsp;거대한&amp;nbsp;배열로&amp;nbsp;다루는&amp;nbsp;언어에서는&amp;nbsp;누릴&amp;nbsp;수&amp;nbsp;없는&amp;nbsp;강점이다.&amp;nbsp;하지만&amp;nbsp;아무리&amp;nbsp;자바라&amp;nbsp;해도&amp;nbsp;다른&amp;nbsp;클래스로부터의&amp;nbsp;침범을&amp;nbsp;아무런&amp;nbsp;노력&amp;nbsp;없이&amp;nbsp;다&amp;nbsp;막을&amp;nbsp;수&amp;nbsp;있는&amp;nbsp;건&amp;nbsp;아니다.&amp;nbsp;그러니&amp;nbsp;클라이언트가&amp;nbsp;불변식을&amp;nbsp;깨뜨리려&amp;nbsp;혈안이&amp;nbsp;되어&amp;nbsp;있다고&amp;nbsp;가정하고&amp;nbsp;방어적으로&amp;nbsp;프로그래밍해야&amp;nbsp;한다.&amp;nbsp;실제로도&amp;nbsp;악의적인&amp;nbsp;의도를&amp;nbsp;가진&amp;nbsp;사람들이&amp;nbsp;시스템의&amp;nbsp;보안을&amp;nbsp;뚫으려는&amp;nbsp;시도가&amp;nbsp;늘고&amp;nbsp;있다.&amp;nbsp;평범한&amp;nbsp;프로그래머도&amp;nbsp;순전히&amp;nbsp;실수로&amp;nbsp;클래스를&amp;nbsp;오작동하게&amp;nbsp;만들&amp;nbsp;수&amp;nbsp;있다.&amp;nbsp;어떤&amp;nbsp;경우든&amp;nbsp;적절치&amp;nbsp;않은&amp;nbsp;클라이언트로부터&amp;nbsp;클래스를&amp;nbsp;보호하는데&amp;nbsp;충분한&amp;nbsp;시간을&amp;nbsp;투자하는&amp;nbsp;게&amp;nbsp;좋다.&amp;nbsp;어떤&amp;nbsp;객체든&amp;nbsp;그&amp;nbsp;객체의&amp;nbsp;허락&amp;nbsp;없이는&amp;nbsp;외부에서&amp;nbsp;내부를&amp;nbsp;수정하는&amp;nbsp;일은&amp;nbsp;불가능하다.&amp;nbsp;하지만&amp;nbsp;주의를&amp;nbsp;기울이지&amp;nbsp;않으면&amp;nbsp;자기도&amp;nbsp;모르게&amp;nbsp;내부를&amp;nbsp;수정하도록&amp;nbsp;허락하는&amp;nbsp;경우가&amp;nbsp;생긴다.&amp;nbsp;예를&amp;nbsp;들어,&amp;nbsp;기간을&amp;nbsp;표현하는&amp;nbsp;Period클래스는&amp;nbsp;얼핏&amp;nbsp;불변처럼&amp;nbsp;보이지만,&amp;nbsp;Date자체가&amp;nbsp;가변적이기&amp;nbsp;때문에&amp;nbsp;불변식을&amp;nbsp;깨뜨릴&amp;nbsp;수&amp;nbsp;있다.&amp;nbsp;다행히&amp;nbsp;자바&amp;nbsp;8&amp;nbsp;이후로는&amp;nbsp;Date&amp;nbsp;대신&amp;nbsp;불변인&amp;nbsp;Instant,&amp;nbsp;LocalDateTime,&amp;nbsp;ZonedDateTime을&amp;nbsp;사용하면&amp;nbsp;된다.&amp;nbsp;Date는&amp;nbsp;낡은&amp;nbsp;API이니&amp;nbsp;새로운&amp;nbsp;코드를&amp;nbsp;작성할&amp;nbsp;때는&amp;nbsp;더&amp;nbsp;이상&amp;nbsp;사용하면&amp;nbsp;안&amp;nbsp;된다.&amp;nbsp;하지만&amp;nbsp;앞으로&amp;nbsp;쓰지&amp;nbsp;않는다고&amp;nbsp;이&amp;nbsp;문제에서&amp;nbsp;해방되는&amp;nbsp;건&amp;nbsp;아니다.&amp;nbsp;Date처럼&amp;nbsp;가변인&amp;nbsp;낡은&amp;nbsp;값&amp;nbsp;타입을&amp;nbsp;사용하던&amp;nbsp;시절이&amp;nbsp;워낙&amp;nbsp;길었던&amp;nbsp;탓에&amp;nbsp;여전히&amp;nbsp;많은&amp;nbsp;API와&amp;nbsp;내부&amp;nbsp;구현에&amp;nbsp;그&amp;nbsp;잔재가&amp;nbsp;남아&amp;nbsp;있다.&amp;nbsp;이번&amp;nbsp;아이템은&amp;nbsp;예전에&amp;nbsp;작성된&amp;nbsp;낡은&amp;nbsp;코드들을&amp;nbsp;대처하기&amp;nbsp;위한&amp;nbsp;것이다.&amp;nbsp;외부&amp;nbsp;공격으로부터&amp;nbsp;Period&amp;nbsp;인스턴스의&amp;nbsp;내부를&amp;nbsp;보호하려면&amp;nbsp;생성자에서&amp;nbsp;받은&amp;nbsp;가변&amp;nbsp;매개변수&amp;nbsp;각각을&amp;nbsp;방어적으로&amp;nbsp;복사해야&amp;nbsp;한다.&amp;nbsp;그런&amp;nbsp;다음&amp;nbsp;Period&amp;nbsp;인스턴스&amp;nbsp;안에서는&amp;nbsp;원본이&amp;nbsp;아닌&amp;nbsp;복사본을&amp;nbsp;사용한다.&amp;nbsp;이렇게&amp;nbsp;사용하면,&amp;nbsp;앞서&amp;nbsp;공격은&amp;nbsp;더&amp;nbsp;이상&amp;nbsp;Period에&amp;nbsp;위협이&amp;nbsp;되지&amp;nbsp;않는다.&amp;nbsp;매개변수의&amp;nbsp;유효성을&amp;nbsp;검사하기&amp;nbsp;전에&amp;nbsp;방어적&amp;nbsp;복사본을&amp;nbsp;만들고,&amp;nbsp;이&amp;nbsp;복사본으로&amp;nbsp;유효성을&amp;nbsp;검사한&amp;nbsp;점에&amp;nbsp;주목하자.&amp;nbsp;순서가&amp;nbsp;부자연스러워&amp;nbsp;보이겠지만&amp;nbsp;반드시&amp;nbsp;이렇게&amp;nbsp;작성해야&amp;nbsp;한다.&amp;nbsp;멀티스레딩&amp;nbsp;환경이라면&amp;nbsp;원본&amp;nbsp;객체의&amp;nbsp;유효성을&amp;nbsp;검사한&amp;nbsp;후&amp;nbsp;복사본을&amp;nbsp;만드는&amp;nbsp;그&amp;nbsp;찰나의&amp;nbsp;취약한&amp;nbsp;순간에&amp;nbsp;다른&amp;nbsp;스레드가&amp;nbsp;원본&amp;nbsp;객체를&amp;nbsp;수정할&amp;nbsp;위험이&amp;nbsp;있기&amp;nbsp;때문이다.&amp;nbsp;방어적&amp;nbsp;복사를&amp;nbsp;매개변수&amp;nbsp;유효성&amp;nbsp;검사&amp;nbsp;전에&amp;nbsp;수행하면&amp;nbsp;이런&amp;nbsp;위험에서&amp;nbsp;해방될&amp;nbsp;수&amp;nbsp;있다.&amp;nbsp;컴퓨터&amp;nbsp;보안&amp;nbsp;커뮤니티에서는&amp;nbsp;이를&amp;nbsp;검사시점/사용&amp;nbsp;시점&amp;nbsp;공격&amp;nbsp;혹은&amp;nbsp;영어&amp;nbsp;표기를&amp;nbsp;줄여서&amp;nbsp;TOCTOU&amp;nbsp;공격이라&amp;nbsp;한다.&amp;nbsp;방어적&amp;nbsp;복사에&amp;nbsp;Date의&amp;nbsp;clone&amp;nbsp;메서드를&amp;nbsp;사용하지&amp;nbsp;않은&amp;nbsp;점에도&amp;nbsp;주목하자.&amp;nbsp;Date는&amp;nbsp;final이&amp;nbsp;아니므로&amp;nbsp;clone이&amp;nbsp;Date가&amp;nbsp;정의한&amp;nbsp;게&amp;nbsp;아닐&amp;nbsp;수&amp;nbsp;있다.&amp;nbsp;즉,&amp;nbsp;clone이&amp;nbsp;악의를&amp;nbsp;가진&amp;nbsp;하위&amp;nbsp;클래스의&amp;nbsp;인스턴스를&amp;nbsp;반환할&amp;nbsp;수도&amp;nbsp;있다.&amp;nbsp;예컨대&amp;nbsp;이&amp;nbsp;하위&amp;nbsp;클래스는&amp;nbsp;start와&amp;nbsp;end&amp;nbsp;필드의&amp;nbsp;참조를&amp;nbsp;private&amp;nbsp;정적&amp;nbsp;리스트에&amp;nbsp;담아뒀다가&amp;nbsp;공격자에게&amp;nbsp;이&amp;nbsp;리스트에&amp;nbsp;접근하는&amp;nbsp;길을&amp;nbsp;열어줄&amp;nbsp;수도&amp;nbsp;있다.&amp;nbsp;결국&amp;nbsp;공격자에게&amp;nbsp;Period&amp;nbsp;인스턴스&amp;nbsp;자체를&amp;nbsp;송두리째&amp;nbsp;맡기는&amp;nbsp;꼴이&amp;nbsp;된다.&amp;nbsp;이런&amp;nbsp;공격을&amp;nbsp;막기&amp;nbsp;위해서는&amp;nbsp;매개변수가&amp;nbsp;제3자에&amp;nbsp;의해&amp;nbsp;확장될&amp;nbsp;수&amp;nbsp;있는&amp;nbsp;타입이라면&amp;nbsp;방어적&amp;nbsp;복사본을&amp;nbsp;만들&amp;nbsp;때&amp;nbsp;clone을&amp;nbsp;사용해서는&amp;nbsp;안&amp;nbsp;된다.&amp;nbsp;생성자를&amp;nbsp;수정하면&amp;nbsp;앞서&amp;nbsp;공격은&amp;nbsp;막아낼&amp;nbsp;수&amp;nbsp;있지만,&amp;nbsp;Period&amp;nbsp;인스턴스는&amp;nbsp;아직도&amp;nbsp;변경&amp;nbsp;가능하다.&amp;nbsp;접근자&amp;nbsp;메서드가&amp;nbsp;내부의&amp;nbsp;가변&amp;nbsp;정보를&amp;nbsp;직접&amp;nbsp;드러내기&amp;nbsp;때문이다.&amp;nbsp;새로운&amp;nbsp;접근자까지&amp;nbsp;갖추면&amp;nbsp;Period는&amp;nbsp;완벽한&amp;nbsp;불변으로&amp;nbsp;거듭난다.&amp;nbsp;아무리&amp;nbsp;악의적인&amp;nbsp;혹은&amp;nbsp;부주의한&amp;nbsp;프로그래머라도&amp;nbsp;시작&amp;nbsp;시각이&amp;nbsp;종료&amp;nbsp;시각보다&amp;nbsp;나중일&amp;nbsp;수&amp;nbsp;없다는&amp;nbsp;불변식을&amp;nbsp;위배할&amp;nbsp;방법은&amp;nbsp;없다.&amp;nbsp;Period&amp;nbsp;자신&amp;nbsp;말고는&amp;nbsp;가변&amp;nbsp;필드에&amp;nbsp;접근할&amp;nbsp;방법이&amp;nbsp;없으니&amp;nbsp;확실하다.&amp;nbsp;모든&amp;nbsp;필드가&amp;nbsp;객체&amp;nbsp;안에&amp;nbsp;완벽하게&amp;nbsp;캡슐화되었다.&amp;nbsp;생성자와&amp;nbsp;달리&amp;nbsp;접근자&amp;nbsp;메서드에서는&amp;nbsp;방어적&amp;nbsp;복사에&amp;nbsp;clone을&amp;nbsp;사용해도&amp;nbsp;된다.&amp;nbsp;Period가&amp;nbsp;가지고&amp;nbsp;있는&amp;nbsp;Date&amp;nbsp;객체는&amp;nbsp;java.util.Date임이&amp;nbsp;확실하기&amp;nbsp;때문이다.&amp;nbsp;그렇더라도&amp;nbsp;인스턴스를&amp;nbsp;복사하는&amp;nbsp;데는&amp;nbsp;일반적으로&amp;nbsp;생성자나&amp;nbsp;정적&amp;nbsp;팩터리를&amp;nbsp;쓰는게&amp;nbsp;좋다.&amp;nbsp;매개변수를&amp;nbsp;방어적으로&amp;nbsp;복사하는&amp;nbsp;목적이&amp;nbsp;불변&amp;nbsp;객체를&amp;nbsp;만들기&amp;nbsp;위해서만은&amp;nbsp;아니다.&amp;nbsp;메서드든&amp;nbsp;생성자든&amp;nbsp;클라이언트가&amp;nbsp;제공한&amp;nbsp;객체의&amp;nbsp;참조를&amp;nbsp;내부의&amp;nbsp;자료구조에&amp;nbsp;보관해야&amp;nbsp;할&amp;nbsp;때면&amp;nbsp;항시&amp;nbsp;그&amp;nbsp;객체가&amp;nbsp;잠재적으로&amp;nbsp;변경될&amp;nbsp;수&amp;nbsp;있는지를&amp;nbsp;생각해야&amp;nbsp;한다.&amp;nbsp;변경될&amp;nbsp;수&amp;nbsp;있는&amp;nbsp;객체라면&amp;nbsp;그&amp;nbsp;객체가&amp;nbsp;클래스에&amp;nbsp;넘겨진&amp;nbsp;뒤&amp;nbsp;임의로&amp;nbsp;변경되어도&amp;nbsp;그&amp;nbsp;클래스가&amp;nbsp;문제없이&amp;nbsp;동작할지를&amp;nbsp;따져보라.&amp;nbsp;확신할&amp;nbsp;수&amp;nbsp;없다면&amp;nbsp;복사본을&amp;nbsp;만들어&amp;nbsp;저장해야&amp;nbsp;한다.&amp;nbsp;예컨대&amp;nbsp;클라이언트가&amp;nbsp;건네준&amp;nbsp;객체를&amp;nbsp;내부의&amp;nbsp;Set&amp;nbsp;인스턴스에&amp;nbsp;저장하거나&amp;nbsp;Map&amp;nbsp;인스턴스의&amp;nbsp;키로&amp;nbsp;사용한다면,&amp;nbsp;추후&amp;nbsp;그&amp;nbsp;객체가&amp;nbsp;변경될&amp;nbsp;경우&amp;nbsp;객체를&amp;nbsp;담고&amp;nbsp;있는&amp;nbsp;Set&amp;nbsp;혹은&amp;nbsp;Map의&amp;nbsp;불변식이&amp;nbsp;깨질&amp;nbsp;것이다.&amp;nbsp;내부&amp;nbsp;객체를&amp;nbsp;클라이언트에&amp;nbsp;건네주기&amp;nbsp;전에&amp;nbsp;방어적&amp;nbsp;복사본을&amp;nbsp;만드는&amp;nbsp;이유도&amp;nbsp;마찬가지다.&amp;nbsp;클래스가&amp;nbsp;불변이든&amp;nbsp;가변이든,&amp;nbsp;가변인&amp;nbsp;내부&amp;nbsp;객체를&amp;nbsp;클라이언트에&amp;nbsp;반환할&amp;nbsp;때는&amp;nbsp;반드시&amp;nbsp;심사숙고해야&amp;nbsp;한다.&amp;nbsp;안심할&amp;nbsp;수&amp;nbsp;없다면&amp;nbsp;방어적&amp;nbsp;복사본을&amp;nbsp;반환해야&amp;nbsp;한다.&amp;nbsp;이상의&amp;nbsp;모든&amp;nbsp;작업에서&amp;nbsp;되도록&amp;nbsp;불변&amp;nbsp;객체들을&amp;nbsp;조합해&amp;nbsp;객체를&amp;nbsp;구성해야&amp;nbsp;방어적&amp;nbsp;복사를&amp;nbsp;할&amp;nbsp;일이&amp;nbsp;줄어든다는&amp;nbsp;교훈을&amp;nbsp;얻을&amp;nbsp;수&amp;nbsp;있다.&amp;nbsp;방어적&amp;nbsp;복사에는&amp;nbsp;성능&amp;nbsp;저하가&amp;nbsp;따르고,&amp;nbsp;항상&amp;nbsp;쓸&amp;nbsp;수&amp;nbsp;있는&amp;nbsp;것도&amp;nbsp;아니다.&amp;nbsp;호출자가&amp;nbsp;컴포넌트&amp;nbsp;내부를&amp;nbsp;수정하지&amp;nbsp;않으리라&amp;nbsp;확신하면&amp;nbsp;방어적&amp;nbsp;복사를&amp;nbsp;생략할&amp;nbsp;수&amp;nbsp;있다.&amp;nbsp;이러한&amp;nbsp;상황이라도&amp;nbsp;호출자에서&amp;nbsp;해당&amp;nbsp;매개변수나&amp;nbsp;반환&amp;nbsp;값을&amp;nbsp;수정하지&amp;nbsp;말아야&amp;nbsp;함을&amp;nbsp;명확히&amp;nbsp;문서화하는&amp;nbsp;게&amp;nbsp;좋다.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;요약하자면,&lt;/b&gt;&lt;br /&gt;클래스가&amp;nbsp;클라이언트로부터&amp;nbsp;받는&amp;nbsp;혹은&amp;nbsp;클라이언트로&amp;nbsp;반환하는&amp;nbsp;구성요소가&amp;nbsp;가변이라면&amp;nbsp;그&amp;nbsp;요소는&amp;nbsp;반드시&amp;nbsp;방어적으로&amp;nbsp;복사해야&amp;nbsp;한다.&amp;nbsp;복사&amp;nbsp;비용이&amp;nbsp;너무&amp;nbsp;크거나&amp;nbsp;클라이언트가&amp;nbsp;그&amp;nbsp;요소를&amp;nbsp;잘못&amp;nbsp;수정할&amp;nbsp;일이&amp;nbsp;없음을&amp;nbsp;신뢰한다면&amp;nbsp;방어적&amp;nbsp;복사를&amp;nbsp;수행하는&amp;nbsp;대신&amp;nbsp;해당&amp;nbsp;구성요소를&amp;nbsp;수정했을&amp;nbsp;때의&amp;nbsp;책임이&amp;nbsp;클라이언트에&amp;nbsp;있음을&amp;nbsp;문서에&amp;nbsp;명시하도록&amp;nbsp;하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출처: 이펙티브 자바 3판&lt;/p&gt;</description>
      <category>IT 개발/Effective Java</category>
      <author>comet observer</author>
      <guid isPermaLink="true">https://comet-info.tistory.com/8</guid>
      <comments>https://comet-info.tistory.com/8#entry8comment</comments>
      <pubDate>Tue, 21 Feb 2023 22:16:28 +0900</pubDate>
    </item>
  </channel>
</rss>