<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>w-beom.log</title>
        <link>https://velog.io/</link>
        <description>습득한 지식과 경험을 나누며 다른 사람들과 문제를 함께 해결해 나가는 과정에서 서로가 성장할 수 있는 기회를 만들고자 노력합니다.</description>
        <lastBuildDate>Tue, 03 Feb 2026 06:34:28 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <copyright>Copyright (C) 2019. w-beom.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/w-beom" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Gitub Actions로 로또 구매 자동화 (동행복권 사이트 리뉴얼)]]></title>
            <link>https://velog.io/@w-beom/Gitub-Actions%EB%A1%9C-%EB%A1%9C%EB%98%90-%EA%B5%AC%EB%A7%A4-%EC%9E%90%EB%8F%99%ED%99%94-%EB%8F%99%ED%96%89%EB%B3%B5%EA%B6%8C-%EC%82%AC%EC%9D%B4%ED%8A%B8-%EB%A6%AC%EB%89%B4%EC%96%BC</link>
            <guid>https://velog.io/@w-beom/Gitub-Actions%EB%A1%9C-%EB%A1%9C%EB%98%90-%EA%B5%AC%EB%A7%A4-%EC%9E%90%EB%8F%99%ED%99%94-%EB%8F%99%ED%96%89%EB%B3%B5%EA%B6%8C-%EC%82%AC%EC%9D%B4%ED%8A%B8-%EB%A6%AC%EB%89%B4%EC%96%BC</guid>
            <pubDate>Tue, 03 Feb 2026 06:34:28 GMT</pubDate>
            <description><![CDATA[<p>얼마전에 동행복권 사이트가 리뉴얼 되고나서 기존에 로또구매자동화를 해놨었던 코드가 정상적으로 작동하지 않고 있었다. 그렇다고 직접 나가서 또 사기에는 귀찮아서 미루고 미루다가 이제야 수정했다.</p>
<pre><code class="language-python">from playwright.sync_api import Playwright, sync_playwright
import time

# 동행복권 아이디와 패스워드를 설정
USER_ID = &#39;id&#39;
USER_PW = &#39;password&#39;

# 구매 개수를 설정
COUNT = 5

def run(playwright: Playwright) -&gt; None:
    # 브라우저 실행
    browser = playwright.chromium.launch(headless=True)
    context = browser.new_context()
    page = context.new_page()

    # 로그인 페이지 이동
    page.goto(&quot;https://dhlottery.co.kr/login&quot;)

    # 로그인
    page.fill(&quot;#inpUserId&quot;, USER_ID)
    page.fill(&quot;#inpUserPswdEncn&quot;, USER_PW)
    with page.expect_navigation():
        page.click(&quot;#btnLogin&quot;)

    time.sleep(5)

    # 로또 구매 페이지 이동
    page.goto(&quot;https://ol.dhlottery.co.kr/olotto/game/game645.do&quot;)
    time.sleep(3)

    # &quot;비정상적인 방법으로 접속하였습니다&quot; 팝업이 있으면 닫기
    popup = page.locator(&quot;#popupLayerAlert&quot;)
    if popup.is_visible(timeout=3000):
        popup.get_by_role(&quot;button&quot;, name=&quot;확인&quot;).click()

    # 자동번호발급 클릭
    page.click(&quot;text=자동번호발급&quot;)
    time.sleep(1)

    # 구매할 개수 선택
    page.select_option(&quot;select&quot;, str(COUNT))
    time.sleep(1)

    # 확인 버튼 클릭
    page.click(&quot;#btnSelectNum&quot;)
    time.sleep(1)

    # 구매하기 버튼 클릭
    page.click(&quot;#btnBuy&quot;)
    time.sleep(1)

    # &quot;구매하시겠습니까?&quot; 팝업에서 확인 클릭
    page.click(&quot;input[onclick=&#39;javascript:closepopupLayerConfirm(true);&#39;]&quot;)
    time.sleep(3)

    # 구매 완료 후 닫기
    page.click(&quot;#closeLayer&quot;)

    context.close()
    browser.close()

with sync_playwright() as playwright:
    run(playwright)
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Github actions로 로또 자동 구매 오류 해결하기]]></title>
            <link>https://velog.io/@w-beom/Github-actions%EB%A1%9C-%EB%A1%9C%EB%98%90-%EC%9E%90%EB%8F%99-%EA%B5%AC%EB%A7%A4%ED%95%98%EA%B8%B0-%EC%98%A4%EB%A5%98-%ED%95%B4%EA%B2%B0%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@w-beom/Github-actions%EB%A1%9C-%EB%A1%9C%EB%98%90-%EC%9E%90%EB%8F%99-%EA%B5%AC%EB%A7%A4%ED%95%98%EA%B8%B0-%EC%98%A4%EB%A5%98-%ED%95%B4%EA%B2%B0%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sat, 07 Jun 2025 09:52:05 GMT</pubDate>
            <description><![CDATA[<p>약 2년전에 <code>Github Actions로 로또 자동 구매하기</code> 포스팅을 보고난 후로 지속적으로 자동 구매를 해오다가 몇 달 전부터 예치금을 충전하지 않아서 지속적으로 이메일로 오류가 난다고 메일이 도착하고 있었습니다.</p>
<p>오랜만에 로또를 다시 구매해보고자 예치금을 충전해놓고 오늘을 기다려왔는데 오늘조차 똑같이 오류메일이 도착해있어서 무슨 문제인가 싶어서 해결하기로 했습니다.</p>
<p>아래 링크는 제가 참고한 포스팅입니다.</p>
<blockquote>
<p><a href="https://qiita.com/leechungkyu/items/223018d712420703c4f3">https://qiita.com/leechungkyu/items/223018d712420703c4f3</a> (Github Actions으로 로또 자동 구매하기 (지속적 로또))</p>
</blockquote>
<h1 id="문제점-파악">문제점 파악</h1>
<p>우선 Github로 가서 오류로그를 먼저 확인해보았는데 해당 오류는 아래와 같았습니다.
<img src="https://velog.velcdn.com/images/w-beom/post/52bcb9ef-6119-4222-8215-a9fdfa1406c8/image.png" alt="">
Python 3.7버전을 찾을 수 없다는 오류였는데요</p>
<pre><code class="language-yml">jobs:
  build:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: [3.7]

    steps:
    - uses: actions/checkout@v2
    - name: Set up python ${{ matrix.python-version }}
      uses: actions/setup-python@v1
      with:
        python-version: ${{ matrix.python-version }}
</code></pre>
<p>위는 Github actions의 <code>workflow.yml</code>의 일부분인데 현재 ubuntu-latest(= Ubuntu 24.04) 이미지에는 3.7 바이너리가 더 이상 들어있지 않아서 발생되는 오류였습니다.</p>
<p>Python버전을 최신버전인 3.11버전으로 설치되도록 수정하였습니다. 아래는 수정된 부분의 파일입니다.</p>
<pre><code class="language-yml">jobs:
  buy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: &#39;3.11&#39;</code></pre>
<p>위와 같이 수정한 후 workflow을 돌려보니 또 다른 오류가 발생했는데</p>
<p><img src="https://velog.velcdn.com/images/w-beom/post/edc38144-1aa6-42a4-896f-c1fc099b656e/image.png" alt="">
이번에는 <code>wget</code> 으로 받아오려던 chromedriver 100 ZIP이 더 이상 서버에 없어서 발생한 오류였습니다.</p>
<pre><code class="language-yml"> # 4. 우분투 패키지 설치
      - name: Install ubuntu packages
        run: |
          sudo apt-get update
          sudo apt-get install -y fonts-unfonts-core fonts-unfonts-extra google-chrome-stable unzip
          wget https://chromedriver.storage.googleapis.com/100.0.4896.20/chromedriver_linux64.zip
          unzip chromedriver_linux64.zip -d $HOME/.local/bin
          chmod +x $HOME/.local/bin/chromedriver
          echo &quot;$HOME/.local/bin&quot; &gt;&gt; $GITHUB_PATH</code></pre>
<p>위의 부분 중간에서 오류가 난 것 같습니다.
Chromedriver 수동 설치 블록을 지우고 python packages 단계에서 Selenium 4.6+가 제공하는 selenium-manager가 알아서 드라이버를 내려받도록 수정하여 아래와 같이 수정하였습니다.</p>
<pre><code class="language-yml">- name: Install Python packages
        run: |
          pip install selenium&gt;=4.6 requests twython pillow \
                     gspread google-api-python-client oauth2client \
                     playwright
          python -m playwright install --with-deps</code></pre>
<h1 id="최종코드">최종코드</h1>
<pre><code class="language-yml">name: Lotto Buy Bot (로또 구매봇)

on:
  # 매주 토요일 08:55 KST → 금요일 23:55 UTC
  schedule:
    - cron: &#39;55 23 * * 5&#39;
  # 수동 실행
  workflow_dispatch:

jobs:
  buy:
    runs-on: ubuntu-latest       # Ubuntu 24.04
    steps:
      # 1. 코드 체크아웃
      - uses: actions/checkout@v4

      # 2. Python 3.11 설치 (캐시 X)
      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: &#39;3.11&#39;

      # 3-A) requirements.txt 없이 직접 패키지 설치
      - name: Install Python packages
        run: |
          pip install selenium&gt;=4.6 requests twython pillow \
                     gspread google-api-python-client oauth2client \
                     playwright
          python -m playwright install --with-deps
      # 4. 우분투 패키지 설치
      - name: Install OS packages
        run: |
          sudo apt-get update
          sudo apt-get install -y fonts-unfonts-core fonts-unfonts-extra \
                                  google-chrome-stable
      # 5. 로또 자동 구매 스크립트 실행
      - name: Run the bot
        run: python ./buy_lotto.py</code></pre>
<p>이번주는 직접 수동으로 로또를 구매하여 다음주까지 한번 결과를 기다려봐야겠네요</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[디자인 패턴 꼭 써야 한다]]></title>
            <link>https://velog.io/@w-beom/%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4-%EA%BC%AD-%EC%8D%A8%EC%95%BC-%ED%95%9C%EB%8B%A4</link>
            <guid>https://velog.io/@w-beom/%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4-%EA%BC%AD-%EC%8D%A8%EC%95%BC-%ED%95%9C%EB%8B%A4</guid>
            <pubDate>Sat, 29 Apr 2023 05:58:03 GMT</pubDate>
            <description><![CDATA[<h1 id="디자인-패턴-꼭-써야-한다">디자인 패턴 꼭 써야 한다.</h1>
<p>아무리 자바 프로그래밍에 대한 지식을 쌓는다 하더라도, 디자인 패턴이나 UML로 작성된 산출물을 이해할 수 없다면 전체적인 큰 그림을 보지 못한다.</p>
<h2 id="mvc-모델">MVC 모델</h2>
<p>J2EE 패턴을 공부하려면, MVC 모델에 대해 먼저 이해해야 한다. 왜냐하면 J2EE 패턴에는 MVC 구조가 기본으로 깔려 있고, 요즘 많이 사용하는 Spring 프레임워크의 Spring MVC도 매우 인기있는 부분이다.</p>
<p>MVC는 Model, View, Controller의 약자이다. 하나의 JSP나 스윙<code>Swing</code> 처럼 화면에 모든 처리 로직을 모아 두는 것이 아니라 모델 역할, 뷰 역할, 컨트롤러 역할을 하는 클래스를 각각 만들어서 개발하는 모델이다.</p>
<h2 id="j2ee-디자인-패턴이란">J2EE 디자인 패턴이란?</h2>
<ul>
<li>Intercepting Filter 패턴 : 요청 타입에 따라 다른 처리를 하기 위한 패턴</li>
<li>Front Controller 패턴 : 요청 전후에 처리하기 위한 컨트롤러를 지정하는 패턴</li>
<li>View Helper 패턴 : 프레젠테이션 로직과 상관 없는 비즈니스 로직을 헬퍼로 지정하는 패턴이다.</li>
<li>Composite View 패턴 : 최소 단위의 하위 컴포넌트를 분리하여 화면을 구성하는 패턴</li>
<li>Service to Worker 패턴 : Front Controller와 View Helper 사이에 디스패처를 두어 조합하는 패턴이다.</li>
<li>Dispatcher View 패턴 : Front Controller와 View Helper로 디스패처 컴포넌트를 형성한다. 뷰 처리가 종료될 때까지 다른 활동을 지연한다는 점이 Service to Worker 패턴과 다르다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Chapter 9. 코드를 재사용하고 일반화할 수 있도록 하라]]></title>
            <link>https://velog.io/@w-beom/Chapter-9.-%EC%BD%94%EB%93%9C%EB%A5%BC-%EC%9E%AC%EC%82%AC%EC%9A%A9%ED%95%98%EA%B3%A0-%EC%9D%BC%EB%B0%98%ED%99%94%ED%95%A0-%EC%88%98-%EC%9E%88%EB%8F%84%EB%A1%9D-%ED%95%98%EB%9D%BC-2y63jt45</link>
            <guid>https://velog.io/@w-beom/Chapter-9.-%EC%BD%94%EB%93%9C%EB%A5%BC-%EC%9E%AC%EC%82%AC%EC%9A%A9%ED%95%98%EA%B3%A0-%EC%9D%BC%EB%B0%98%ED%99%94%ED%95%A0-%EC%88%98-%EC%9E%88%EB%8F%84%EB%A1%9D-%ED%95%98%EB%9D%BC-2y63jt45</guid>
            <pubDate>Fri, 28 Apr 2023 12:02:58 GMT</pubDate>
            <description><![CDATA[<h1 id="chapter-9-코드를-재사용하고-일반화할-수-있도록-하라">Chapter 9. 코드를 재사용하고 일반화할 수 있도록 하라</h1>
<p>■ 안전하게 재사용할 수 있는 코드 작성 방법
■ 다양한 문제를 해결하기 위해 일반화된 코드를 작성하는 방법</p>
<h2 id="91-가정을-주의하라">9.1 가정을 주의하라</h2>
<p>코드 작성 시 가정을 하면 코드가 더 단순해지거나, 더 효율적으로 되거나, 둘 다일 수도 있다. 그러나 이러한 가정으로 인해 코드가 더 취약해지고 활용도가 낮아져 재사용하기에 안전하지 않을 수 있다.</p>
<h3 id="911-가정은-코드-재사용-시-버그를-초래할-수-있다">9.1.1 가정은 코드 재사용 시 버그를 초래할 수 있다.</h3>
<pre><code class="language-java">class Article {
  private List &lt;Section&gt; sections;
  List&lt;Image&gt; getAllImages() {
    for (Section section in sections) {
      if (section.containsImages()) { // 기사 내에 이미지를 포함하는 섹션은 최대 하나만 있다. 
        return section.getImages();
      }
    }
    return new ArrayList&lt;&gt;();
  }
}
</code></pre>
<p><code>Article</code> 클래스는 사용자가 읽을 수 있는 뉴스 웹 사이트의 기사를 나타낸다. 
<code>getAllImages()</code> 함수는 기사에 포함된 모든 이미지를 반환한다. 이 코드는 이미지가 포함된 섹션이 <code>하나만 있을 것이라고 가정</code>한다. 이러한 가정을 함으로써 이미지가 있는 섹션이 두 개 이상인 기사에 <code>getAllImages()</code> 함수가 사용될 경우 모든 이미지를 반환하지 않는다. 이런 일은 일어날 가능성이 매우 높고, 그로 인해 버그를 초래할 가능성이 있다.</p>
<h3 id="912-해결책--불필요한-가정을-피하라">9.1.2 해결책 : 불필요한 가정을 피하라</h3>
<p>책에서는 불필요한 가정을 피하기 위해 비용-이익 상충관계를 고려하고 섣부른 최적화를 하지 말라고 한다.</p>
<blockquote>
<p><strong>섣부른 최적화</strong>
코드 최적화는 일반적으로 비용이 든다. 즉, 최적화된 해결책을 구현하는 데 더 많은 시간과 노력이 필요하며 그 결과 코드는 종종 가독성이 떨어지고, 유지 관리가 더 어려워지며, 가정을 하게 되면 견고함이 떨어질 가능성이 있다. 게다가 최적화는 보통 프로그램 내에서 수천 번 혹은 수백만 번 실행되는 코드 부분에 대해 이루어질 때 상당한 이점이 있다.<br>
따라서 대부분의 경우에는 큰 효과 없는 코드 최적화를 하느라고 애쓰기보다는 코드를 읽을 수 있고, 유지보수 가능하며, 견고하게 만드는 데 집중하는 것이 좋다. 코드의 어떤 부분이 여러 번 실행되고 그 부분을 최적화하는 것이 성능 향상에 큰 효과를 볼 수 있다는 점이 명백해질 때에라야 최적화 작업을 해도 무방하다.</p>
</blockquote>
<h3 id="913-해결책--가정이-필요하면-강제적으로-하라">9.1.3 해결책 : 가정이 필요하면 강제적으로 하라</h3>
<p>때로는 가정이 필요하거나 가정으로 얻는 이득이 비용을 초과할 정도로 코드가 단순해질 수 있다. 코드에 가정이 있을 때, 다른 개발자들이 그것을 여전히 모를 수 있다는 사실을 염두에 두어야 한다.</p>
<ol>
<li><strong>가정이 &#39;꺠지지 않게&#39; 만들라</strong> : 가정이 깨지면 컴파일되지 않는 방식으로 코드를 작성할 수 있다면 가정이 항상 유지될 수 있다.</li>
<li><strong>오류 전달 기술을 사용하라</strong> : 가정을 깨는 것이 불가능하게 만들 수 없는 경우에는 오류를 감지하고 오류 신호 전달 기술을 사용하여 신속하게 실패하도록 코드를 작성할 수 있다.</li>
</ol>
<h2 id="92-전역-상태를-주의하라">9.2 전역 상태를 주의하라</h2>
<p><code>전역 상태</code> 또는 <code>전역변수</code>는 실행되는 프로그램 내의 모든 콘텍스트 사이에 공유된다. 전역변수를 정의하는 일반적인 방법은 다음과 같다.</p>
<ul>
<li>자바나 C# 같은 언어에서 변수를 정적static으로 표시(이 책의 의사코드에서 사용되는 방식이다)</li>
<li>C++와 같은 언어에서 클래스나 함수의 외부 즉 파일 수준의 변수 정의</li>
<li>자바스크립트 기반 언어에서 전역 윈도 객체의 속성으로 정의</li>
</ul>
<p><code>전역변수</code>는 프로그램 내의 모든 콘텍스트에 영향을 미치기 때문에 전역변수를 사용할 때는 누구도 해당 코드를 다른 목적으로 재사용하지 않을 것이라는 암묵적인 가정을 전제한다. 전역 상태는 코드를 매우 취약하게 만들고 재사용하기도 안전하지 않기 때문에 일반적으로 이점보다 비용이 더 크다.</p>
<h3 id="921-전역-상태를-갖는-코드는-재사용하기에-안전하지-않을-수-있다">9.2.1 전역 상태를 갖는 코드는 재사용하기에 안전하지 않을 수 있다.</h3>
<p>어떤 상태에 대해 프로그램의 여러 부분이 공유하고 접근할 필요가 있을 때 이것을 전역변수에 넣고 싶은 마음이 들 수 있다. 이렇게 하면 코드의 어느 부분이라도 그 상태에 접근하기가 아주 쉽다. 이렇게 하면 코드를 재사용하는 것이 안전하지 않을 때가 있다.</p>
<p>온라인 쇼핑 애플리케이션을 만들고 있다고 가정해보자.
사용자의 장바구니 항목은 아이템을 추가하는 코드, 사용자가 장바구니의 항목을 검토하는 화면, 체크아웃을 처리하는 코드와 같이 애플리케이션의 많은 다른 부분으로부터 접근하기 위해 아래와 같이 <code>static</code>변수로 선언했다.</p>
<pre><code class="language-java">class ShoppingBasket {
  private static List &lt;Item&gt; items = new ArrayList&lt;&gt;();
  static void addItem(Item item) {
    items.add(item);
  }
  static void List &lt;Item&gt; getItems() {
    return List.copyOf(items);
  }
}</code></pre>
<pre><code class="language-java">class ViewItemWidget {
  private final Item item;

  ViewItemWidget(Item item) {
    this.item = item;
  }
  void addItemToBasket() {
    ShoppingBasket.addItem(item);
  }
}
class ViewBasketWidget {
  void displayItems() {
    List&lt;Item&gt; items = ShoppingBasket.getItems();
  }
}</code></pre>
<p><strong>누군가 이 코드를 재사용하려고 하면 어떻게 되는가?</strong>
알았든 몰랐든 간에 이 코드를 작성할 때 암묵적인 가정이 이루어졌는데, 그것은 이 소프트웨어를 실행하는 인스턴스당 하나의 장바구니만 필요하다는 것이었다. 이 가정이 맞지 않게 될 상황이 많은데 이것은 이 가정이 상당히 취약하다는 것을 의미한다. 이 가정은 다음과 같은 이유로 깨질 수 있다.</p>
<ul>
<li>사용자의 장바구니 내용을 서버에 저장하기로 결정하고 서버단 코드에서 <code>ShoppingBasket</code> 클래스를 사용하기 시작한다. 서버의 한 인스턴스는 다른 많은 사용자로부터의 요청을 처리하기 때문에 이제 소프트웨어를 실행하는 인스턴스당 (이 경우 서버) 장바구니는 많이 존재한다.</li>
<li>사용자가 나중을 위해 장바구니 항목을 저장할 수 있는 기능을 추가한다. 즉, 클라이언트 측 응용 프로그램은 현재 활성 장바구니뿐만 아니라 나중을 위해 저장되는 장바구니까지 처리해야 한다.</li>
<li>정상적인 재고 외에 신선한 농산물을 판매하기 시작한다. 이것은 완전히 다른 공급자와 배송 메커니즘을 사용하기 때문에 별도의 장바구니로 처리해야 한다.</li>
</ul>
<p>원래의 가정이 깨지면 소프트웨어에 문제가 발생한다. 만약 두 개의 다른 코드에서 모두 <code>ShoppingBasket</code> 클래스를 사용하고 있다면 그것들은 서로 간섭할 것이다 이들 중 하나가 항목을 추가하면 이 항목은 장바구니를 사용하는 다른 모든 코드에도 추가되어 보일 것이다. 위에서 예로 든 상황에서는 버그가 발생할 수 있으므로 <code>ShoppingBasket</code> 클래스는 안전한 방법으로 재사용하기가 불가능하다.</p>
<h3 id="922-해결책-공유-상태에-의존성-주입하라">9.2.2 해결책: 공유 상태에 의존성 주입하라</h3>
<p><strong>수정된 ShoppingBasket</strong></p>
<pre><code class="language-java">class ShoppingBasket {
  private final List &lt;Item&gt; items = new ArrayList&lt;&gt;(); // static이 아닌 인스턴스 변수

  void addItem(Item item) {
    items.add(item);
  }

  void List &lt;Item&gt; getItems() {
    return List.copyOf(items);
  }
}</code></pre>
<p><strong>의존성 주입된 ShoppingBasket</strong></p>
<pre><code class="language-java">class ViewItemWidget {
  private final Item item;
  private final ShoppingBasket basket;

  publicViewItemWidget(Item item, ShoppingBasket basket) { // 의존성이 주입된 ShoppingBasket
    this.item = item;
    this.basket = basket;
  }
  public void addItemToBasket() {
    basket.addItem(item);
  }
}

class ViewBasketWidget {
  private final ShoppingBasket basket;

  public ViewBasketWidget(ShoppingBasket basket) { // 의존성이 주입된 ShoppingBasket
    this.basket = basket;
  }
  public void displayItems() {
    List &lt;Item&gt; items = basket.getItems();
  }
}</code></pre>
<h2 id="93-기본-반환값을-적절하게-사용하라">9.3 기본 반환값을 적절하게 사용하라</h2>
<p>합리적인 기본값은 사용자 친화적인 소프트웨어를 만들기 위한 좋은 방법이다. 워드프로세서 프로그램을 열 때마다 단어를 입력하기 전에 항상 원하는 글꼴, 텍스트 크기, 텍스트 색, 배경색, 줄 간격 및 줄 높이를 정확하게 선택해야 한다고 상상해보라. 그 소프트웨어는 사용하기에 매우 짜증 날 것이고, 아마 다른 워드프로세서로 바꿀 것이다.</p>
<p>기본값을 제공하려면 종종 다음과 같은 두 가지 가정이 필요하다.</p>
<ul>
<li>어떤 기본값이 합리적인지</li>
<li>더 상위 계층의 코드는 기본값을 받든지 명시적으로 설정된 값을 받든지 상관하지 않는다.</li>
</ul>
<h3 id="931-낮은-층위의-코드의-기본-반환값은-재사용성을-해칠-수-있다">9.3.1 낮은 층위의 코드의 기본 반환값은 재사용성을 해칠 수 있다.</h3>
<pre><code class="language-java">class UserDocumentSettings {
  private final Font font;

  public Font getPreferredFont() {
    if (font != null) {
      return font;
    }
    return Font.ARIAL;
  }
}</code></pre>
<p>워드프로세서를 개발하고 있다고 가정해보자.
<code>UserDocumentSettings</code> 클래스는 특정 문서에 대한 사용자의 환경설정을 저장하며, 그중에 한 가지가 사용할 글꼴이다. 글꼴을 지정하지 않은 경우 <code>getPreferredFont()</code> 함수는 기본값인 <code>Font.ARIAL</code>을 반환한다.</p>
<p>이렇게 하면 방금 언급한 요구 사항은 충족되지만, 기본 글꼴로 <code>Arial</code>을 원하지 않는 경우에 <code>UserDocumentSettings</code> 클래스를 다시 사용하고자 하는 경우 어려움을 겪을 수 있다. 사용자가 특별히 <code>Arial</code>을 선택한 것인지 아니면 선호하는 폰트를 설정하지 않아 기본값이 반환된 것인지 구분하는 것이 불가능하다.</p>
<h3 id="932-해결책-상위-수준의-코드에서-기본값을-제공하라">9.3.2 해결책: 상위 수준의 코드에서 기본값을 제공하라.</h3>
<p>기본값에 대한 결정을 UserDocumentSettings 클래스에서 하지 않도록 하기 위한 가장 간단한 방법은 사용자가 제공한 값이 없을 때 널값을 반환하는 것이다.</p>
<pre><code class="language-java">class UserDocumentSettings {
  private final Font font;
  public Font getPreferredFont() {
    return font;
  }
}</code></pre>
<p>따라서 기본값 제공은 사용자 설정 처리와는 다른 별개의 하위 문제가 된다. 이는 호출하는 쪽에서 원하는 방식으로 이 하위 문제를 해결할 수 있다는 것을 의미하며, 코드의 재사용성을 높여준다.</p>
<p><strong>기본값을 캡슐화하기 위한 클래스</strong></p>
<pre><code class="language-java">class DefaultDocumentSettings {
  Font getDefaultFont() {
    return Font.ARIAL;
  }
}</code></pre>
<pre><code class="language-java">class DocumentSettings {
  private final UserDocumentSettings userSettings;
  private final DefaultDocumentSettings defaultSettings;

  public DocumentSettings(UserDocumentSettings userSettings, DefaultDocumentSettings defaultSettings) {
    this.userSettings = userSettings;
    this.defaultSettings = defaultSettings;
  }

  public Font getFont() {
    public Font userFont = userSettings.getPreferredFont();
    if (userFont != null) {
      return userFont;
    }
    return defaultSettings.getFont();
  }
}</code></pre>
<p>상위 수준의 코드에서 사용할 설정을 결정할 때 <code>DocumentSettings</code> 클래스는 간결한 추상화 계층을 제공한다. 기본값과 사용자가 제공한 값에 대한 모든 구현 세부 정보를 숨기지만, 동시에 의존성 주입을 사용하여 이러한 구현 세부 정보를 재설정할 수도 있다. 이를 통해 코드의 재사용성과 적응성이 보장된다.</p>
<h2 id="94-함수의-매개변수를-주목하라">9.4 함수의 매개변수를 주목하라</h2>
<h3 id="941-필요-이상으로-매개변수를-받는-함수는-재사용하기-어려울-수-있다">9.4.1 필요 이상으로 매개변수를 받는 함수는 재사용하기 어려울 수 있다.</h3>
<p><strong>필요 이상의 매개변수를 받는 함수</strong></p>
<pre><code class="language-java">class TextBox {
  private final Element textContainer;
  void setTextStyle(TextOptions options) {
    setFont(...);
    setFontSize(...);
    setLineHight(...);
    setTextColor(options);
  }
  void setTextColor(TextOptions options) {
    textContainer.setStyleProperty(&quot;color&quot;, options.getTextColor().asHexRgb());
  }
}</code></pre>
<p>경고 메시지에 대한 스타일을 <code>TextBox</code>에 적용하는 함수를 구현한다고 가정해보자. 이 함수의 요구 사항은 텍스트 색상만 빨간색으로 설정하고 그 외의 모든 스타일 정보는 그대로 유지한다. 이를 위해 <code>TextBox.setTextColor()</code> 함수를 재사용하려 하겠지만 이 함수는 <code>TextOptions</code>의 인스턴스를 매개변수로 사용하기 때문에 간단치가 않다.</p>
<p>텍스트 색상을 빨간색으로 설정하는 것만을 원하지만 이를 위해 그 외의 다른 관련 없는 값으로 전체 <code>TextOptions</code> 인스턴스를 생성해야만 한다.</p>
<h3 id="942-해결책-함수는-필요한-것만-매개변수로-받도록-하라">9.4.2 해결책: 함수는 필요한 것만 매개변수로 받도록 하라.</h3>
<pre><code class="language-java">class TextBox {
  private final Element textElement;

  void setTextStyle(TextOptions options) {
    setFont(...);
    setFontSize(...);
    setLineHight(...);
    setTextColor(options.getTextColor());
  }

  void setTextColor(Color color) {
    textElement.setStyleProperty(&quot;color&quot;, color.asHexRgb());
  }
}</code></pre>
<p><code>TextBox.setTextColor()</code> 함수가 <code>TextOptions</code>에서 유일하게 사용하는 것은 텍스트 색상이다. 따라서 함수는 전체 <code>TextOptions</code> 인스턴스를 사용하는 대신 <code>Color</code> 인스턴스를 매개변수로 사용할 수 있다.</p>
<h2 id="95-제네릭의-사용을-고려하라">9.5 제네릭의 사용을 고려하라</h2>
<p>클래스는 종종 다른 유형 혹은 클래스의 인스턴스나 참조를 갖는다. 이것의 대표적인 예는 리스트 클래스다. 문자열 리스트가 있으면 리스트 클래스는 문자열 클래스의 인스턴스를 갖는다. 리스트에 항목을 저장하는 것은 매우 일반적인 하위 문제다. 어떤 경우에는 문자열 리스트가 필요하지만 다른 경우에는 정수 리스트가 필요할 수 있다. 문자열과 정수를 저장하기 위해 완전히 다른 별개의 리스트 클래스가 필요하다면 상당히 번거로울 것이다.</p>
<h3 id="951-특정-유형에-의존하면-일반화를-제한한다">9.5.1 특정 유형에 의존하면 일반화를 제한한다.</h3>
<p><strong>문자열 유형을 하드 코딩해서 사용</strong></p>
<pre><code class="language-java">class RandomizedQueue {
  private final List&lt;String&gt; values = new ArrayList&lt;&gt;();
  public void add(String value) {
    values.add(value);
  } 

  /** 
  * 큐로부터 무작위로 한 항목을 삭제하고 그 항목을 반환한다. 
  */
  public String getNext() {
    if (values.isEmpty()) {
      return null;
    }
    Integer randomIndex = Math.randomInt(0, values.size());
    values.swap(randomIndex, values.size() - 1);
    return values.removeLast();
  }
}</code></pre>
<p>RandomizedQueue 클래스는 String에 대한 의존도가 높기 때문에 다른 유형을 저장하는 데는 사용할 수 없다. 이 코드에서와 같이 RandomizedQueue를 구현하면 문자열로 표현될 수 있는 단어를 저장하는 특정한 유형의 문제는 해결하지만 다른 유형의 동일한 하위 문제를 해결할 수 있을 만큼 일반화되어 있지는 않다.</p>
<h3 id="952-해결책-제네릭을-사용하라">9.5.2 해결책: 제네릭을 사용하라</h3>
<p><strong>제네릭 사용</strong></p>
<pre><code class="language-java">class RandomizedQueue &lt;T&gt; {
  private final List&lt;T&gt; values = new ArrayList&lt;&gt;();
  void add(T value) {
    values.add(value);
  } 

  /** 
  * 큐에서 무작위로 한 항목을 삭제한 후에 그 항목을 반환한다. 
  */
  public T getNext() {
    if (values.isEmpty()) {
      return null;
    }
    Integer randomIndex = Math.randomInt(0, values.size());
    values.swap(randomIndex, values.size() - 1);
    return values.removeLast();
  }
}</code></pre>
<p>RandomizedQueue 클래스는 이제 원하는 어떤 것이라도 저장할 수 있으므로 단어를 사용하는 게임 버전에서는 다음과 같이 문자열을 저장하는 클래스로 정의할 수 있다.</p>
<pre><code class="language-java">RandomizedQueue&lt;String&gt; words = new RandomizedQueue&lt;String&gt;();

RandomizedQueue&lt;Picture&gt; pictures = new RandomizedQueue&lt;Picture&gt;();</code></pre>
<h2 id="요약">요약</h2>
<ul>
<li><p>동일한 하위 문제가 자주 발생하므로 코드를 재사용하면 미래의 자신과 팀 동료의 시간과 노력을 절약할 수 있다.</p>
</li>
<li><p>다른 개발자가 여러분이 해결하려는 문제와는 다른 상위 수준의 문제를 해결하더라도 특정 하위 문제에 대해서는 자신이 작성한 해결책을 재사용할 수 있도록 근본적인 하위 문제를 식별하고 코드를 구성하도록 노력해야 한다.</p>
</li>
<li><p>간결한 추상화 계층을 만들고 코드를 모듈식으로 만들면 코드를 재사용하고 일반화하기가 훨씬 쉽고 안전해진다.</p>
</li>
<li><p>가정을 하게 되면 코드는 종종 더 취약해지고 재사용하기 어렵다는 측면에서 비용이 발생한다.</p>
<ul>
<li>가정을 하는 경우의 이점이 비용보다 큰지 확인하라.</li>
<li>가정을 해야 할 경우 그 가정이 코드의 적절한 계층에 대해 이루어지는 것인지 확인하고 가능하다면 가정을 강제적으로 적용하라.</li>
</ul>
</li>
<li><p>전역 상태를 사용하면 특히 비용이 많이 발생하는 가정을 하는 것이 되고 재사용하기에 전혀 안전하지 않은 코드가 된다. 대부분의 경우 전역 상태를 피하는 것이 가장 바람직하다.</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Chapter 8. 코드를 모듈화하라 ]]></title>
            <link>https://velog.io/@w-beom/Chapter-8.-%EC%BD%94%EB%93%9C%EB%A5%BC-%EB%AA%A8%EB%93%88%ED%99%94%ED%95%98%EB%9D%BC</link>
            <guid>https://velog.io/@w-beom/Chapter-8.-%EC%BD%94%EB%93%9C%EB%A5%BC-%EB%AA%A8%EB%93%88%ED%99%94%ED%95%98%EB%9D%BC</guid>
            <pubDate>Sun, 23 Apr 2023 12:05:10 GMT</pubDate>
            <description><![CDATA[<h1 id="chapter-8-코드를-모듈화하라">Chapter 8. 코드를 모듈화하라</h1>
<p>■ 모듈화된 코드의 이점
■ 이상적인 코드 모듈화가 되지 않는 일반적인 방식
■ 코드를 좀 더 모듈화하기 위한 방법</p>
<p>모듈화의 주된 목적 중 하나는 코드가 향후에 어떻게 변경되거나 재구성될지 정확히 알지 못한 상태
에서 변경과 재구성이 용이한 코드를 작성하는 것이다. 이를 달성하기 위한 핵심 목표는 각각의 기능 (또는 요구 사항)이 코드베이스의 서로 다른 부분에서 구현되어야 한다는 것이다.</p>
<h2 id="81-의존성-주입의-사용을-고려하라">8.1 의존성 주입의 사용을 고려하라.</h2>
<h3 id="811-하드-코드화된-의존성은-문제가-될-수-있다">8.1.1 하드 코드화된 의존성은 문제가 될 수 있다.</h3>
<pre><code class="language-java">
class RoutePlanner {
  private final RoadMap roadMap;

  RoutePlanner() {
      // roadMap은 interface
    // NorthAmericaRoadMap 객체를 직접 생성하고 있다.
    this.roadMap = new NorthAmericaRoadMap(); 
  }
  Route planRoute(LatLong startPoint, LatLong endPoint) {
    ...
  }
}


interface RoadMap {
  List &lt;Road&gt; getRoads();
  List &lt;Junction&gt; getJunctions();
}

class NorthAmericaRoadMap implements RoadMap {
  @Override 
  List &lt;Road&gt; getRoads() {
    ...
  }
  @Override 
  List &lt;Junction&gt; getJunctions() {
    ...
  }
}
</code></pre>
<p>예제 코드는 자동차 여행 플래너를 구현하는 클래스를 보여준다. <code>RoutePlanner</code> 클래스는 <code>RoadMap</code> 인스턴스에 대한 의존성을 갖는다. <code>RoadMap</code>은 여러 개의 다른 구현체를 갖는 인터페이스다. 그러나 이 예제에서 <code>RoutePlanner</code> 클래스는 생성자에서 <code>NorthAmericaRoadMap</code>을 생성하는데, 이는 <code>RoadMap</code>의 특정 구현체에 대한 의존성이 하드 코드로 되어 있음을 의미한다. 따라서 <code>RoutePlanner</code> 클래스는 북미 여행 계획에만 사용될 수 있고 그 외의 다른 지역을 여행하는 데는 전혀 쓸모가 없다.</p>
<p>위와 같이 <code>RoadMap</code>의 구현체에 의존해서 코드를 구현하면 다른 구현으로 코드를 재설정할 수 없다.</p>
<h2 id="812-해결책--의존성-주입을-사용하라">8.1.2 해결책 : 의존성 주입을 사용하라.</h2>
<pre><code class="language-java">class RoutePlanner {
  private final RoadMap roadMap;

  RoutePlanner(RoadMap roadMap) {
    this.roadMap = roadMap;
  }

  Route planRoute(LatLong startPoint, LatLong endPoint) {
    ...
  }
}</code></pre>
<p>이제 원하는 로드맵을 사용하여 <code>RoutePlanner</code>의 인스턴스를 생성할 수 있다.</p>
<pre><code class="language-java">RoutePlanner europeRoutePlanner = new RoutePlanner(new EuropeRoadMap());

RoutePlanner northAmericaRoutePlanner = new RoutePlanner(new NorthAmericaRoadMap(true, false));</code></pre>
<h2 id="813-의존성-주입을-염두에-두고-코드를-설계하라">8.1.3 의존성 주입을 염두에 두고 코드를 설계하라.</h2>
<p>코드를 작성하다 보면 나중에 의존성 주입을 사용하고 싶어도 사용이 거의 불가능한 코드가 짜여질 수 있기 때문에 이후에 의존성 주입을 사용할 가능성이 있다면 이런 방식으로 코드를 작성하는 것은 피해야 한다.</p>
<p><strong>static 함수에 의존하는 예제</strong></p>
<pre><code class="language-java">class RoutePlanner {
  Route planRoute(LatLong startPoint, LatLong endPoint) {
    List &lt;Road&gt; roads = NorthAmericaRoadMap.getRoads();
    List &lt;Junction&gt; junctions = NorthAmericaRoadMap.getJunctions();
  }
}

class NorthAmericaRoadMap {
  static List &lt;Road&gt; getRoads() {}
  static List &lt;Junction&gt; getJunctions() {}
}</code></pre>
<p><code>RoutePlanner</code> 클래스 안에서 <code>NorthAmericaRoadMap</code> 객체의 static 함수를 사용하고 있어 <code>RoutePlanner</code> 클래스를 다른 방향의 코드로 전환하기가 어렵다.</p>
<blockquote>
<p><strong>정적매달림</strong>
정적 함수(또는 변수)에 과도하게 의존하는 것을 정적 매달림<code>(static cling)</code>이라고 한다. 이에 대한 잠재적 문제는 잘 알려져 있고 문서화도 잘 되어 있다. 단위 테스트 코드에서 특히 문제가 될 수 있는데, 그 이유는 정적 매달림이 많은 코드에 대해서는 테스트 더블<code>(test doubles)</code>을 사용할 수 없기 때문이다.</p>
</blockquote>
<h2 id="82-인터페이스에-의존하라">8.2 인터페이스에 의존하라.</h2>
<p>어떤 클래스에 의존하고 있는데 그 클래스가 어떤 인터페이스를 구현하고 필요한 기능이 그 인터페이스에 모두 정의되어 있으면, 클래스에 직접 의존하기보다는 인터페이스에 의존하는 것이 일반적으로 더 바람직하다.</p>
<h3 id="821-구체적인-구현에-의존하면-적응성이-제한된다">8.2.1 구체적인 구현에 의존하면 적응성이 제한된다.</h3>
<pre><code class="language-java">interface RoadMap {
  List &lt;Road&gt; getRoads();
  List &lt;Junction&gt; getJunctions();
}

class NorthAmericaRoadMap implements RoadMap {
  ...
}

class RoutePlanner {
  private final NorthAmericaRoadMap roadMap;

  RoutePlanner(NorthAmericaRoadMap roadMap) {
    this.roadMap = roadMap;
  }
  Route planRoute(LatLong startPoint, LatLong endPoint) {
    ...
  }
}</code></pre>
<p><code>NorthAmericaRoadMap</code>은 <code>interface</code>인 <code>RoadMap</code>의 구현체이다. <code>RoutePlanner</code> 클래스에서 생성자로 의존성 주입을 받고있지만 <code>interface</code>에 의존하는것이 아닌 <code>RoadMap</code>의 특정 구현체인 <code>NorthAmericaRadMap</code>에 의존하고 있어 유연하지 못하다.</p>
<h2 id="822-해결책--가능한-경우-인터페이스에-의존하라">8.2.2 해결책 : 가능한 경우 인터페이스에 의존하라.</h2>
<p>구체적인 구현 클래스에 의존하면 인터페이스를 의존할 때보다 적응성이 제한되는 경우가 많다.</p>
<pre><code class="language-java">class RoutePlanner {
  private final RoadMap roadMap;

  RoutePlanner(RoadMap roadMap) {
    this.roadMap = roadMap;
  }
  Route planRoute(LatLong startPoint, LatLong endPoint) {
    ...
  }
}</code></pre>
<p><code>interface</code>에 의존하게 됨으로써 개발자는 원하는 로드맵은 무엇이든 사용해서 <code>RoutePlanner</code>의 인스턴스를 생성할 수 있다.</p>
<blockquote>
<p><strong>의존성 역전 원리</strong><br>보다 구체적인 구현보다는 추상화에 의존하는 것이 낫다는 생각은 의존성 역전 원리<code>(dependency inversion principle)</code>의 핵심이다.</p>
</blockquote>
<h2 id="83-클래스-상속을-주의하라">8.3 클래스 상속을 주의하라.</h2>
<h3 id="831-클래스-상속은-문제가-될-수-있다">8.3.1 클래스 상속은 문제가 될 수 있다.</h3>
<p>쉼표로 구분된 정수를 가지고 있는 파일을 열어 정수를 하나씩 읽어 들이는 클래스를 작성해야 한다고 가정해보자. 이 문제에 대해 생각해보면 다음과 같은 하위 문제를 파악할 수 있다.</p>
<ul>
<li>파일에서 데이터를 읽는다.</li>
<li>쉼표로 구분된 파일 내용을 개별 문자열로 나눈다.</li>
<li>각 문자열을 정수로 변환한다.</li>
</ul>
<p><strong>CSV 파일을 읽는 클래스</strong>    </p>
<pre><code class="language-java">interface FileValueReader {
  String getNextValue();
  void close();
}
interface FileValueWriter {
  void writeValue(String value);
  void close();
} 

/*** 쉼표로 구분된 값을 가지고 있는 파일을 읽거나 쓰기 위한 * 유틸리티 */
class CsvFileHandler implements FileValueReader, FileValueWriter {
  CsvFileReader(File file) {
    ...
  }
  @Override 
  public String getNextValue() {
    ...
  }
  @Override 
  public void writeValue(String value) {
    ...
  }
  @Override 
  public void close() {
    ...
  }
}</code></pre>
<p><code>CsvFileHandler</code> 클래스는 <code>FileValueReader</code>와 <code>FileValueWriter</code>의 두 가지 인터페이스를 구현한다.</p>
<p><strong>클래스 상속예제</strong></p>
<pre><code class="language-java">/*** 파일로부터 숫자를 하나씩 읽어 들이는 유틸리티 * 파일은 쉼표로 구분된 값을 가지고 있어야 한다. */
class IntFileReader extends CsvFileHandler {
  public IntFileReader(File file) {
    super(file);
  }

  public Integer getNextInt() {
    String nextValue = getNextValue();
    if (nextValue == null) {
      return null;
    }
    return Integer.parseInt(nextValue, Radix.BASE_10);
  }
}</code></pre>
<p><code>CsvFileHandler</code> 클래스를 사용하여 상위 수준의 문제를 해결하려면 이 클래스를 우리가 작성할 코드에 통합해야 한다.</p>
<ul>
<li><code>IntFileReader</code> 클래스는 <code>CsvFileHandler</code> 클래스를 확장한다. 즉, <code>IntFileReader</code>는 <code>CsvFileHandler</code>의 서브클래스 혹은 <code>CsvFileHandler</code> 클래스는 <code>IntFileReader</code>의 슈퍼클래스라는 의미다.</li>
<li><code>IntFileReader</code> 생성자는 <code>CsvFileHandler</code>의 생성자를 호출하여 <code>CsvFileHandler</code> 인스턴스를 만들어야 한다. 이 작업은 super()를 호출하여 수행한다.</li>
<li><code>IntFileReader</code> 클래스는 슈퍼클래스인 <code>CsvFileHandler</code>의 함수를 마치 자신의 함수인 것처럼 액세스할 수 있으므로 <code>IntFileReader</code> 클래스 내에서 <code>NextValue()</code>를 호출하면 슈퍼클래스의 함수가 호출된다.</li>
</ul>
<p>상속의 주요 특징 중 하나는 서브클래스가 슈퍼클래스에 의해 제공되는 모든 기능을 상속한다는 점인데, 따라서 <code>IntFileReader</code> 클래스의 인스턴스는 <code>close()</code> 함수와 같이 <code>CsvFileHandler</code>에 의해 제공된 함수 중 어느 것이라도 호출할 수 있다.</p>
<p><strong>상속은 추상화 계층에 방해가 될 수 있다.</strong></p>
<p>한 클래스가 다른 클래스를 확장하면 슈퍼클래스의 모든 기능을 상속한다. 이 기능은 <code>close()</code> 함수의 경우처럼 유용할 때가 있지만, 원하는 것보다 더 많은 기능을 노출할 수도 있다. 이로 인해 추상화 계층이 복잡해지고 구현 세부 정보가 드러날 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/w-beom/post/5b85817d-aa2d-4dc7-b4f0-c0dfeaed1482/image.png" alt=""></p>
<p>클래스의 일부 기능을 외부로 개방하는 경우 적어도 그 기능을 사용하는 개발자가 있을 것이라고 예상할 수 있다. 몇 개월 혹은 몇 년 후 코드베이스 곳곳에서 <code>getNextValue()</code> 및 <code>writeValue()</code> 함수가 호출될 수 있다. 이렇게 되면 <code>IntFileReader</code> 클래스를 변경하기가 매우 어렵다.</p>
<h2 id="832-해결책--구성을-사용하라">8.3.2 해결책 : 구성을 사용하라.</h2>
<p>상속을 사용한 원래 동기는 <code>IntFileReader</code> 클래스를 구현하는 데 도움이 되고자 <code>CsvFileHandler</code> 클래스의 일부 기능을 재사용하는 것이었다.</p>
<p><code>CsvFileHandler</code>의 기능을 재사용하는 다른 방법으로는 <code>구성</code>을 사용하는 것이다. 즉, 클래스를 확장하기보다는 해당 클래스의 인스턴스를 가지고 있음으로써 하나의 클래스를 다른 클래스로부터 <code>구성</code> <code>compose</code>한다는 것을 의미한다.</p>
<p><img src="https://velog.velcdn.com/images/w-beom/post/0459c96d-58d9-429a-8da7-c66c6f391283/image.png" alt=""></p>
<ul>
<li><code>FileValueReader</code> 인터페이스는 구현하려는 기능을 정의하고 있기 때문에 <code>CsvFileHandler</code> 클래스를 직접 사용하는 대신 <code>FileValueReader</code> 인터페이스를 사용한다. 따라서 추상화 계층이 더 간결해지고 코드는 재설정하기가 쉬워진다.</li>
<li><code>IntFileReader</code> 클래스는 <code>CsvFileHandler</code> 클래스를 확장하는 대신 <code>FileValueReader</code>의 인스턴스를 참조할 멤버 변수를 갖는다. 이런 의미에서 <code>IntFileReader</code> 클래스는 <code>FileValueReader</code>의 인스턴스로 이루어져 있다(이것이 구성으로 불리는 이유다).</li>
<li><code>FileValueReader</code>의 인스턴스는 <code>IntFileReader</code> 클래스의 생성자를 통해 의존성 주입으로 제공된다.</li>
<li><code>IntFileReader</code> 클래스는 더 이상 <code>CsvFileHandler</code> 클래스를 확장하지 않으므로 <code>close()</code> 메서드를 상속하지 않는다. 대신 <code>IntFileReader</code> 클래스의 사용자가 파일을 닫을 수 있도록 이 클래스에 <code>close()</code> 함수를 추가한다. 이 함수는 <code>FileValueReader</code> 인스턴스의 <code>close()</code> 함수를 호출한다. <code>IntFileReader.close()</code> 함수는 파일을 닫는 명령을 <code>FileValueReader.close()</code> 함수로 전달하기 때문에 이를 전달<code>forwarding</code>이라고 한다.</li>
</ul>
<h3 id="833-진정한-is-a-관계는-어떤가">8.3.3 진정한 is-a 관계는 어떤가?</h3>
<p>두 클래스가 진정한 <code>is-a</code> 관계를 맺고 있다면 상속이 타당할 수 있다고 언급했다. 그러나 두 클래스가 진정으로 <code>is-a</code> 관계일 때조차 상속하는 것이 좋은 접근법인지에 대해서는 명확하지 않을 수 있다. 안타깝게도 이에 대한 답은 없으며 주어진 상황과 작업 중인 코드에 따라 다르다. 하지만 진정한 <code>is-a</code> 관계가 있다 하더라도 상속은 여전히 문제가 될 수 있다는 점을 알아야 한다.</p>
<ul>
<li><p>취약한 베이스 클래스 문제 : 서브클래스가 슈퍼클래스에서 상속되고 해당 슈퍼클래스가 나중에 수정되면 서브클래스가 작동하지 않을 수도 있다. 따라서 코드를 변경할 때 그 변경이 문제없을지 판단하기가 어려운 경우가 있을 수 있다.</p>
</li>
<li><p>문제가 있는 계층 구조 : 많은 언어가 다중 상속을 지원하지 않으므로 클래스는 오직 하나의 클래스만 직접 확장할 수 있다. </p>
</li>
</ul>
<h2 id="84-클래스는-자신의-기능에만-집중해야-한다">8.4 클래스는 자신의 기능에만 집중해야 한다.</h2>
<p>모듈화의 핵심 목표 중 하나는 요구 사항이 변경되면 그 변경과 직접 관련된 코드만 수정한다는 것이다. 단일 개념이 단일 클래스 내에 완전히 포함된 경우라면 이 목표는 달성할 수 있다. 어떤 개념과 관련된 요구 사항이 변경되면 그 개념에 해당하는 단 하나의 클래스만 수정하면 된다.</p>
<h3 id="841-다른-클래스와-지나치게-연관되어-있으면-문제가-될-수-있다">8.4.1 다른 클래스와 지나치게 연관되어 있으면 문제가 될 수 있다.</h3>
<p><img src="https://velog.velcdn.com/images/w-beom/post/015cea70-ce00-4c7c-aca0-d357176e7729/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/w-beom/post/fc0a16c6-bc5d-4d80-b850-f460718038c9/image.png" alt=""></p>
<p><code>Book</code> 클래스에는 장의 단어 수를 세는 <code>getChapterWordCount()</code> 함수가 포함되어 있다. 이 함수는 <code>Book</code> 클래스에 속해 있지만 <code>Chapter</code> 클래스에만 관련된 사항을 다룬다. 이것은 <code>Chapter</code> 클래스에 대한 많은 세부 사항이 <code>Book</code> 클래스에 하드 코딩된다는 것을 의미한다.</p>
<p><code>getChapterWordCount()</code> 함수를 <code>Book</code> 클래스에 두면 코드가 모듈화되지 않는다. 요구 사항이 변경되어 장의 끝에 요약을 포함해야 한다면, <code>getChapterWordCount()</code> 기능도 수정해서 요약에 있는 단어들도 셀 수 있도록 해야 한다.</p>
<h3 id="842-해결책--자신의-기능에만-충실한-클래스를-만들라">8.4.2 해결책 : 자신의 기능에만 충실한 클래스를 만들라</h3>
<p>코드 모듈화를 유지하고 한 가지 사항에 대한 변경 사항이 코드의 한 부분만 영향을 미치도록 하기 위해, <code>Book</code>과 <code>Chapter</code> 클래스는 가능한 한 자신의 기능에만 충실하도록 해야 한다.
<img src="https://velog.velcdn.com/images/w-beom/post/45e44a38-2644-45a6-9f1c-c79ed10334a0/image.png" alt="">
<img src="https://velog.velcdn.com/images/w-beom/post/a4436bb5-bc92-403b-a32d-b8b2f73aaf9b/image.png" alt=""></p>
<p>이제 <code>Chapter</code> 클래스에는 <code>wordCount()</code>라는 멤버 함수가 있으며 <code>Book</code> 클래스는 이 함수를 사용한다. <code>Book</code> 클래스는 <code>Chapter</code> 클래스의 세부 사항을 다룰 필요가 없고 자기 자신만 신경 쓰면 된다. 장 끝에 요약이 있어야 하는 것으로 요구 사항이 변경된 경우 <code>Chapter</code> 클래스만 수정하면 된다.</p>
<blockquote>
<p><strong>디미터의 법칙</strong>
디미터의 법칙은 한 객체가 다른 객체의 내용이나 구조에 대해 가능한 한 최대한으로 가정하지 않아야 한다는 소프트웨어 공학의 원칙이다. 이 원칙은 특히 한 객체는 직접 관련된 객체와만 상호작용해야 한다고 주장한다.<br>
디미터의 법칙에 의하면 <code>Book</code> 클래스는 <code>Chapter</code> 클래스 인스턴스와만 상호작용해야 하고 그 외의 어떤 객체와도, 예를 들어 서두와 절을 나타내는 <code>TextBlock</code>과는 상호작용하지 않아야 한다. <br>
원래 코드에 있는 <code>Chapter.getPreude().wordCount()</code>와 같은 라인은 명백하게 이 점을 위반하고 있기 때문에 이 경우 디미터의 법칙을 사용한다면 원본 코드의 문제를 발견할 수 있을 것이다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Chapter 7. 코드를 오용하기 어렵게 만들라]]></title>
            <link>https://velog.io/@w-beom/Chapter-7.-%EC%BD%94%EB%93%9C%EB%A5%BC-%EC%98%A4%EC%9A%A9%ED%95%98%EA%B8%B0-%EC%96%B4%EB%A0%B5%EA%B2%8C-%EB%A7%8C%EB%93%A4%EB%9D%BC</link>
            <guid>https://velog.io/@w-beom/Chapter-7.-%EC%BD%94%EB%93%9C%EB%A5%BC-%EC%98%A4%EC%9A%A9%ED%95%98%EA%B8%B0-%EC%96%B4%EB%A0%B5%EA%B2%8C-%EB%A7%8C%EB%93%A4%EB%9D%BC</guid>
            <pubDate>Sun, 16 Apr 2023 08:10:39 GMT</pubDate>
            <description><![CDATA[<h1 id="chapter-7-코드를-오용하기-어렵게-만들라">Chapter 7. 코드를 오용하기 어렵게 만들라</h1>
<p>■ 코드 오남용으로 인해 버그가 발생하는 방식
■ 코드를 오용하기 쉬운 흔한 방식
■ 코드를 오용하기 어렵게 만드는 기술</p>
<p>코드가 오용하기 쉽게 작성된다면, 조만간 오용될 가능성이 있고 소프트웨어가 올바르게 작동하지 않을 것이다.
비합리적이거나 애매한 가정에 기반해서 코드가 작성되거나 다른 개발자가 잘못된 일을 하는 것을 막지 못할 때 코드는 오용되기 쉽다.
코드를 잘못 사용할 수 있는 몇 가지 일반적인 경우는 다음과 같다.</p>
<ul>
<li>호출하는 쪽에서 잘못된 입력을 제공</li>
<li>다른 코드의 부수 효과(side effect) (입력 매개변수 수정 등)</li>
<li>정확한 시간이나 순서에 따라 함수를 호출하지 않음</li>
<li>관련 코드에서 가정과 맞지 않게 수정이 이루어짐</li>
</ul>
<h2 id="71-불변-객체로-만드는-것을-고려하라">7.1 불변 객체로 만드는 것을 고려하라.</h2>
<p>객체가 생성된 후에 상태를 바꿀 수 없다면 이 객체는 <code>불변immutable</code>이다. 불변성이 바람직한 이유를 이해하기 위해서는 그 반대인 가변 <code>mutability</code> 객체가 어떻게 문제를 일으킬 수 있는지 고려해야 한다. </p>
<h3 id="가변-객체가-일으키는-문제">가변 객체가 일으키는 문제</h3>
<ul>
<li><strong>가변 객체는 추론하기 어렵다.</strong> <ul>
<li>요즘 치킨 배달을 시키면 포장 박스에 봉인씰이 붙여져서 온다. 봉인씰은 붙인 후에 누군가가 떼면 자국이 선명하게 생기기 때문에 누군가가 뜯었다는 증거가 된다. 하지만 봉인씰을 뜯었다고 안에 들어있는 치킨에 어떤 짓을 했는지는 뜯은 사람만 알고있다. 사실 봉인씰만 뜯고 치킨에는 아무짓도 안했을 수도 있는 것이다.
코드를 작성할 때 만약 객체가 불변이라면, 그것은 마치 누구도 해제할 수 없는 변질 방지 봉인을 붙여 놓는 것과 같다. 객체를 여기저기에 전달하더라도 어디서도 그 객체가 변경됐거나 무언가 추가되지 않았다는 것을 확신할 수 있다.</li>
</ul>
</li>
<li><strong>가변 객체는 다중 스레드에서 문제가 발생할 수 있다.</strong><ul>
<li>객체가 가변적이면 해당 객체를 사용하는 다중 스레드 코드가 특히 취약할 수 있다. 한 스레드가 객체를 읽는 동안 다른 스레드가 그 객체를 수정하는 경우 오류가 발생할 수 있다. 예를 들어 한 스레드가 리스트에서 마지막 요소를 제거하는 동안 다른 스레드가 그 요소를 읽으려는 경우다.</li>
</ul>
</li>
</ul>
<p>객체를 불변으로 만드는 것이 항상 가능하지도 않고, 또 항상 적절한 것도 아니다. 필연적으로 상태 변화를 추적해야 하는 경우가 있고 이때는 가변적인 자료구조가 필요하다. 하지만 방금 설명했듯이 가변적인 객체는 코드의 복잡성을 늘리고 문제를 일으킬 수 있기 때문에, 기본적으로는 불변적인 객체를 만들되 필요한 곳에서만 가변적이 되도록 하는 것이 바람직하다.</p>
<h3 id="711-가변-클래스는-오용하기-쉽다">7.1.1 가변 클래스는 오용하기 쉽다.</h3>
<p>클래스를 가변적으로 만드는 가장 일반적인 방법은 세터setter 함수를 제공하는 것이다.</p>
<pre><code class="language-java">class User{
    private String name;
    private int age;

       void setName(String name){ //setter 함수를 이용해 User의 name을 수정할 수 있다.
        this.name = name;
    }
    void setAge(int age){
        this.age = age;
    }
}</code></pre>
<h3 id="가변성으로-인한-버그">가변성으로 인한 버그</h3>
<pre><code class="language-java">// User의 name을 출력하는 함수
public void printName(User user) {
    doSomething(user);
    System.out.println(uset.getName());
}

private void doSomething(User user){
    // doSomething
    user.setName(&quot;A&quot;); // 이 메소드에서 user의 name을 강제로 A로 set해버린다.
}</code></pre>
<h3 id="712-해결책-객체를-생성할-때만-값을-할당하라">7.1.2 해결책: 객체를 생성할 때만 값을 할당하라</h3>
<p>클래스 내에서 변수를 정의할 때 심지어 클래스 내에서도 변수의 값이 변경되지 않도록 할 수 있다. 이 방법은 언어에 따라 다른데 공통적으로 사용하는 키워드는 const, final, readonly이다.</p>
<pre><code class="language-java">class User{
    private final String name;
       private final int age;

    public User(String name, int age){
        this.name = name;
        this.age = age;
    }

    public getName() {
        return this.name;
    }
    public getAge() {
        return this.age;
    }
}</code></pre>
<h3 id="713-해결책-불변성에-대한-디자인-패턴을-사용하라">7.1.3 해결책: 불변성에 대한 디자인 패턴을 사용하라</h3>
<p>이를 위한 두 가지 유용한 디자인 패턴은 다음과 같다.</p>
<ul>
<li>빌더 패턴</li>
<li>쓰기 시 복사 패턴</li>
</ul>
<h2 id="72-객체를-깊은-수준까지-불변적으로-만드는-것을-고려하라">7.2 객체를 깊은 수준까지 불변적으로 만드는 것을 고려하라</h2>
<p>클래스가 실수로 가변적으로 될 수 있는 일반적인 경우는 <code>깊은 가변성(deep mutability)</code> 때문이다. 이 문제는 멤버 변수 자체가 가변적인 유형이고 다른 코드가 멤버 변수에 액세스할 수 있는 경우에 발생할 수 있다.</p>
<pre><code class="language-java">class Users {
    private final List&lt;User&gt; users;

    public List&lt;User&gt; getUsers(){
        return this.users;
    }
}

public void doSomething(Users users){
    List&lt;User&gt; list = users.getUsers();
    list.add(new User()); // 깊은 가변성으로 인해 users의 멤버변수가 수정된다.
}</code></pre>
<h3 id="722-해결책-방어적으로-복사하라">7.2.2 해결책: 방어적으로 복사하라</h3>
<pre><code class="language-java">class Users {
    private final List&lt;User&gt; users;

    public List&lt;User&gt; getUsers(){
        return new List&lt;User&gt;(users);
    }
}

public void doSomething(Users users){
    List&lt;User&gt; list = users.getUsers();
    list.add(new User()); // getUser메소드 호출 시 새로운 List객체가 반환 되므로 users의 멤버변수의 users List는 변경이 없다.
}</code></pre>
<h3 id="723-해결책-불변적-자료구조를-사용하라">7.2.3 해결책: 불변적 자료구조를 사용하라</h3>
<pre><code class="language-java">class Users {
    private final List&lt;User&gt; users;

    public List&lt;User&gt; getUsers(){
        return Collections.unmodifiableList(this.users);
    }
}

public void doSomething(Users users){
    List&lt;User&gt; list = users.getUsers();
    list.add(new User()); // list가 불변객체이므로 add메소드 호출 시 exception이 발생한다.
}</code></pre>
<h2 id="73-지나치게-일반적인-데이터-유형을-피하라">7.3 지나치게 일반적인 데이터 유형을 피하라</h2>
<p>정수, 문자열 및 리스트 같은 간단한 데이터 유형은 코드의 기본적인 구성 요소 중 하나다. 정수나 리스트와 같은 유형으로 표현이 <strong>‘가능’</strong> 하다고 해서 그것이 반드시 <strong>‘좋은’</strong> 방법은 아니다. 설명이 부족하고 허용하는 범위가 넓을수록 코드 오용은 쉬워진다.</p>
<h3 id="731-지나치게-일반적인-유형은-오용될-수-있다">7.3.1 지나치게 일반적인 유형은 오용될 수 있다.</h3>
<p>예를 들어 2D 지도의 위치는 위도와 경도에 대한 두 가지 값이 모두 필요하다. 지도에서 위치를 처리하는 코드를 작성할 경우 위치를 나타내는 자료구조가 필요하다. 자료구조에는 해당 위치의 위도와 경도에 대한 값이 모두 포함되어야 한다.</p>
<p><strong>지나치게 일반적인 데이터 유형</strong></p>
<pre><code class="language-java">class LocationDisplay {
  private final DrawableMap map;

  /**
  * 지도 위에 제공된 모든 좌표의 위치를 표시한다.
  *
  * 리스트의 리스트를 받아들이는데, 내부의 리스트는 정확히
  * 두 개의 값을 가지고 있다. 첫 번째 값은 위치의 위도이고
  * 두 번째 값은 경도를 나타낸다(둘 다 각도 값).
  */
  void markLocationsOnMap(List&lt;List&lt;Double&gt;&gt; locations) {
    for (List&lt;Double&gt; location in locations) {
      map.markLocation(location[0], location[1]);
    }
  }
}</code></pre>
<p>코드를 오용하기 쉽게 만드는 단점이 있는데 다음과 같다.</p>
<ul>
<li><code>List&lt;List&lt;Double&gt;&gt;</code> 유형 자체로는 아무것도 설명해주지 않는다. 개발자가 markLocationsOn
Map() 함수에 대한 주석문을 읽어보지 않는다면 이 리스트가 무엇인지, 어떻게 이해해야 하는지 알지 못할 것이다.</li>
<li>개발자는 리스트에서 어떤 항목이 위도와 경도인지 혼동하기 쉽다. 만약 주석문을 완전히 읽지 않았거나 잘못 이해했다면, 위도와 경도의 순서를 뒤바꿀 수 있고, 이로 인해 버그가 발생할 것
이다.</li>
<li>형식 안전성이 거의 없다. 컴파일러가 목록 내에 몇 개의 요소가 있는지 보장할 수 없다. 그림 7.5에서 볼 수 있듯이 내부 리스트에 들어 있는 항목의 수가 잘못될 수 있다. 이렇게 되면 코드가 정상적으로 컴파일되지만 런타임에 문제가 일어난다.</li>
</ul>
<h3 id="732-페어-유형은-오용하기-쉽다">7.3.2 페어 유형은 오용하기 쉽다.</h3>
<h3 id="733-해결책-전용-유형-사용">7.3.3 해결책: 전용 유형 사용</h3>
<p>지도의 2D 위치를 나타내는 경우 코드의 오용 혹은 오해의 소지를 줄이는 간단한 방법은 위도와 경도를 나타내는 전용 클래스를 정의하는 것이다.</p>
<pre><code class="language-java">/**
* 위도와 경도를 각도로 나타낸다.
*/
class LatLong {
  private final Double latitude;
  private final Double longitude;
  LatLong(Double latitude, Double longitude) {
    this.latitude = latitude;
    this.longitude = longitude;
  }
  Double getLatitude() {
    return latitude;
  }
  Double getLongitude() {
    return longitude;
  }
}</code></pre>
<pre><code class="language-java">class LocationDisplay {
  private final DrawableMap map;

  /**
  * 지도 위에 제공된 모든 좌표의 위치를 표시한다.
  **/
  void markLocationsOnMap(List&lt;LatLong&gt; locations) {
    for (LatLong location in locations) {
      map.markLocation(location.getLatitude(), location.getLongitude());
    }
  }
}</code></pre>
<h2 id="74-시간-처리">7.4 시간 처리</h2>
<p>시간을 다룰 때 코드를 잘못 사용하고 혼동을 일으킬 여지가 굉장히 많다.</p>
<ul>
<li>어떤 때는 ‘1969년 7월 21일 02:56 UTC’와 같이 절대적인 시간을 지칭하지만 또 다른 때는 ‘5분 내’와 같은 상대적인 시간으로 표현한다.</li>
<li>오븐에서 30분 굽기’와 같은 시간의 양을 언급하는 경우도 있다. 시간은 분, 초, 밀리초 등 다양한 단위 중 하나로 표시할 수 있다.</li>
<li>표준 시간대, 일광 절약 시간, 윤년leap year, 심지어 윤초leap second와 같은 개념도 있어서 상황이 훨씬 더 복잡하다.</li>
</ul>
<h3 id="741-정수로-시간을-나타내는-것은-문제가-될-수-있다">7.4.1 정수로 시간을 나타내는 것은 문제가 될 수 있다.</h3>
<p>시간을 나타낼 때 일반적으로 정수나 큰 정수long integer를 사용한다. 이것으로 어느 한순간을 의미하는 시각과 시간의 양, 두 가지를 모두 나타낸다.
정수는 매우 일반적인 유형이기 때문에 시간을 나타내는 데 사용하는 경우 코드가 오용되기 쉽다</p>
<h3 id="742-해결책-적절한-자료구조를-사용하라">7.4.2 해결책: 적절한 자료구조를 사용하라.</h3>
<ul>
<li>양으로서의 시간과 순간으로서의 시간의 구분
Java.time 패키지에서 Instant라는 클래스와 Duration이라는 클래스를 제공한다. </li>
</ul>
<pre><code class="language-java">/** 
* @param message 보낼 메시지 
* @param deadline 데드라인이 경과하기까지 메시지가 전송 
* 되지 않으면 전송은 취소된다. 
* @return 메시지가 전송되면 참을, 그렇지 않으면 거짓 
*/

Boolean sendMessage(String message, Duration deadline) {
    ...
}</code></pre>
<p><strong>더 이상 단위에 대한 혼동이 없다.</strong>
단위가 사용되어야 하는지 설명하기 위한 계약의 세부 조항이 필요하지 않으며, 실수로 잘못된 단위를 제공하는 것이 불가능하다.</p>
<pre><code class="language-java">Duration duration1 = Duration.ofSeconds(5);
print(duration1.toMillis()); // 출력: 5000
Duration duration2 = Duration.ofMinutes(2);
print(duration2.toMillis()); // 출력: 120000</code></pre>
<h2 id="75-데이터에-대해-진실의-원천을-하나만-가져야-한다">7.5 데이터에 대해 진실의 원천을 하나만 가져야 한다.</h2>
<p>코드에서 숫자, 문자열, 바이트 스트림과 같은 종류의 데이터를 처리하는 경우가 많다. 데이터는 종종 두 가지 형태로 제공된다.</p>
<ul>
<li>기본 데이터 <code>primary data</code>: 코드에 제공해야 할 데이터. 코드에 이 데이터를 알려주지 않고는 코드가 처리할 방법이 없다.</li>
<li>파생 데이터 <code>derived data</code>: 주어진 기본 데이터에 기반해서 코드가 계산할 수 있는 데이터</li>
</ul>
<p>예를 들어 은행계좌의 상태를 설명하는 데 필요한 데이터가 있을 수 있다. 여기에서 기본 데이터는 대변<code>credit</code> 금액과 차변<code>debit</code> 금액이다. 계좌 잔액은 파생 데이터인데 대변에서 차변을 뺀 금액이다.</p>
<p>기본 데이터는 일반적으로 프로그램에서 진실의 원천source of truth이 된다. 대변과 차변에 대한 값은 계좌의 상태를 완전히 설명하고 계좌의 상태를 추적하기 위해 저장되어야 하는 유일한 값이다.</p>
<h3 id="751-또-다른-진실의-원천은-유효하지-않은-상태를-초래할-수-있다">7.5.1 또 다른 진실의 원천은 유효하지 않은 상태를 초래할 수 있다.</h3>
<p>은행계좌의 경우 계좌 잔고액은 두 가지 기본 데이터에 의해 완전히 제한된다. 대변이 5달러이고 차변이 2달러인 상태에서 잔액이 10달러라고 하는 것은 말이 안 된다.</p>
<p>기본 데이터와 파생 데이터를 모두 처리하는 코드를 작성할 때, 이와 같이 논리적으로 잘못된 상태가 발생할 수 있다. 논리적으로 잘못된 상태가 발생할 수 있는 코드를 작성하면 코드의 오용이 너무 쉬워진다.</p>
<pre><code class="language-java">class UserAccount { 
  private final Double credit; 
  private final Double debit; 
  private final Double balance; 

  UserAccount(Double credit, Double debit, Double balance) { // 대변, 차변, 잔액이 모두 생성자에 전달된다.
    this.credit = credit; 
    this.debit = debit; 
    this.balance = balance; 
  } 
  Double getCredit() { 
    return credit; 
  } 
  Double getDebit() { 
    return debit; 
  } 
  Double getBalance() { 
    return balance; 
  }
}


// 잔액이 차변에서 대변을 빼는 잘못된 방법으로 계산된다.
UserAccount account = new UserAccount(credit, debit, debit - credit); </code></pre>
<h3 id="752-해결책-기본-데이터를-유일한-진실의-원천으로-사용하라">7.5.2 해결책: 기본 데이터를 유일한 진실의 원천으로 사용하라</h3>
<pre><code class="language-java">class UserAccount { 
  private final Double credit; 
  private final Double debit;

  UserAccount(Double credit, Double debit) { 
    this.credit = credit; 
    this.debit = debit; 
  } 
  Double getCredit() { 
    return credit; 
  } 
  Double getDebit() { 
    return debit; 
  } 
  Double getBalance() {  // 잔액은 대변과 차변으로 계산된다.
    return credit - debit; 
  }
}</code></pre>
<h2 id="76-논리에-대한-진실의-원천을-하나만-가져야-한다">7.6 논리에 대한 진실의 원천을 하나만 가져야 한다.</h2>
<p><strong>진실의 원천 <code>sources of truth</code></strong>은 코드에 제공된 데이터에만 적용되는 것이 아니라 코드에 포함된 논리에도 적용된다.</p>
<h3 id="761-논리에-대한-진실의-원천이-여러-개-있으면-버그를-유발할-수-있다">7.6.1 논리에 대한 진실의 원천이 여러 개 있으면 버그를 유발할 수 있다.</h3>
<p>정숫값을 기록한 후에 파일로 저장하는 클래스를 보여준다. 이 코드에는 값이 파일에 저장되는 방식에 대한 두 가지 중요한 세부 정보가 있다.</p>
<ol>
<li>각 값은 10진수 문자열 형식으로 변환된다.</li>
<li>그다음 각 값의 문자열을 쉼표로 구분하여 결합한다.</li>
</ol>
<pre><code class="language-java">class DataLogger { 
  private final List&lt;Int&gt; loggedValues; 

  public void saveValues(FileHandler file) {
    String serializedValues = loggedValues.map(value -&gt; value.toString(Radix.BASE_10)).join(&quot;,&quot;); 
    file.write(serializedValues); 
  }
}</code></pre>
<p>DataLogger.saveValues()의 반대 과정, 즉 파일을 열고 정수로 읽어 들이는 코드도 어딘가에 있을 가능성이 크다.</p>
<p>이 코드는 DataLogger 클래스와 완전히 다른 파일이고 코드베이스의 다른 부분에 있을 가능성이 크지만 논리는 서로 일치해야 한다.</p>
<ol>
<li>문자열을 쉼표로 구분해서 분할해 문자열의 목록을 만든다.</li>
<li>목록의 각 문자열을 십진수 정수로 읽는다.</li>
</ol>
<pre><code class="language-java">class DataLoader { 
  public List&lt;Integer&gt; loadValues(FileHandler file) { 
    return file.readAsString().split(&quot;,&quot;).map(str -&gt; Int.parse(str, Radix.BASE_10)); 
}</code></pre>
<p>이 시나리오에서 값이 파일에 저장되는 형식은 논리의 중요한 부분이지만, 이 형식이 무엇인지에 대해서는 <strong>진실의 원천이 두 개 존재한다</strong>.
클래스가 모두 동일한 논리를 포함하면 모든 것이 잘 작동하지만 한 클래스가 수정되고 다른 클래스가 수정되지 않으면 문제가 발생한다.</p>
<h3 id="762-해결책-진실의-원천은-단-하나만-있어야-한다">7.6.2 해결책: 진실의 원천은 단 하나만 있어야 한다.</h3>
<p><img src="https://velog.velcdn.com/images/w-beom/post/28356bb9-2f7b-487d-b8ee-3c7645cfe17e/image.png" alt="">
직렬화된 정수를 저장하는 형식에 대한 진실의 원천을 하나만 갖게 되면 코드가 더 견고해지고 오류의 가능성을 줄일 수 있다.</p>
<pre><code class="language-java">class IntListFormat {
    private const String DELIMITER = &quot;,&quot;;
    private const Radix RADIX = Radix.BASE_10;
    String serialize(List &lt;Integer&gt; values) {
        return values.map(value - &gt; value.toString(RADIX)).join(DELIMITER);
    }
    List &lt;Integer&gt; deserialize(String serialized) {
        return serialized.split(DELIMITER).map(str - &gt; Int.parse(str, RADIX));
    }
}</code></pre>
<pre><code class="language-java">class DataLogger {
    private final List&lt;Integer&gt; loggedValues;
    private final IntListFormat intListFormat;

    public void saveValues(FileHandler file) {
        file.write(intListFormat.serialize(loggedValues));
    }
}

class DataLoader {
    private final IntListFormat intListFormat;

    List &lt;Integer&gt; loadValues(FileHandler file) {
        return intListFormat.deserialize(file.readAsString());
    }
}</code></pre>
<p>이렇게 하면 앞서 <code>DataLogger</code> 클래스에서 사용하는 형식을 변경하지만 실수로 <code>DataLoader</code> 클래스는 변경하지 않는 것과 같은 위험은 제거된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[HttpMediaTypeNotAcceptableException : No acceptable representation 해결]]></title>
            <link>https://velog.io/@w-beom/No-acceptable-representation-%ED%95%B4%EA%B2%B0</link>
            <guid>https://velog.io/@w-beom/No-acceptable-representation-%ED%95%B4%EA%B2%B0</guid>
            <pubDate>Wed, 12 Apr 2023 15:41:18 GMT</pubDate>
            <description><![CDATA[<p><code>@ControllerAdvice</code>를 이용한 글로벌 예외처리 작업을 하다가 예외가 발생했습니다.</p>
<pre><code class="language-java">org.springframework.web.HttpMediaTypeNotAcceptableException: No acceptable representation
    at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:322) ~[spring-webmvc-6.0.6.jar:6.0.6]
    at org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor.handleReturnValue(HttpEntityMethodProcessor.java:245) ~[spring-webmvc-6.0.6.jar:6.0.6]
    ...</code></pre>
<p>아래는 예외가 발생한 코드입니다.</p>
<pre><code class="language-java">@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity&lt;ErrorResponse&gt; methodArgumentNotValidExceptionHandler(final MethodArgumentNotValidException ex) {
        ErrorResponse errorResponse = new ErrorResponse(ex.getMessage(), HttpStatus.BAD_REQUEST);
        return ResponseEntity.badRequest().body(errorResponse);
    }
}</code></pre>
<p>ReponseEntity로 반환하는 ErrorReponse객체에 getter가 없어서 예외가 발생하였습니다.</p>
<h3 id="수정-전">수정 전</h3>
<pre><code class="language-java">public class ErrorResponse {
    private final String message;
    private final HttpStatus httpStatus;

    public ErrorResponse(String message, HttpStatus httpStatus) {
        this.message = message;
        this.httpStatus = httpStatus;
    }
}</code></pre>
<h3 id="수정-후">수정 후</h3>
<pre><code class="language-java">@Getter
public class ErrorResponse {
    private final String message;
    private final HttpStatus httpStatus;

    public ErrorResponse(String message, HttpStatus httpStatus) {
        this.message = message;
        this.httpStatus = httpStatus;
    }
}
</code></pre>
<p>저는 Lobmok 라이브러리를 사용중이라 @Getter 어노테이션을 추가하였습니다. 
Lombok을 사용하지 않는다면 따로 getter 메소드를 생성하면 될 것 같습니다.</p>
<p>참고 : <a href="https://stackoverflow.com/questions/28466207/could-not-find-acceptable-representation-using-spring-boot-starter-web">https://stackoverflow.com/questions/28466207/could-not-find-acceptable-representation-using-spring-boot-starter-web</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[예측 가능한 코드를 작성하라]]></title>
            <link>https://velog.io/@w-beom/%EC%98%88%EC%B8%A1-%EA%B0%80%EB%8A%A5%ED%95%9C-%EC%BD%94%EB%93%9C%EB%A5%BC-%EC%9E%91%EC%84%B1%ED%95%98%EB%9D%BC</link>
            <guid>https://velog.io/@w-beom/%EC%98%88%EC%B8%A1-%EA%B0%80%EB%8A%A5%ED%95%9C-%EC%BD%94%EB%93%9C%EB%A5%BC-%EC%9E%91%EC%84%B1%ED%95%98%EB%9D%BC</guid>
            <pubDate>Sun, 09 Apr 2023 07:55:45 GMT</pubDate>
            <description><![CDATA[<h1 id="chapter-6-예측-가능한-코드를-작성하라">Chapter 6. 예측 가능한 코드를 작성하라</h1>
<p>■ 코드가 어떻게 예측을 벗어나 작동할 수 있는지
■ 소프트웨어에서 예측을 벗어나는 코드가 어떻게 버그로 이어질 수 있는지
■ 코드가 예측을 벗어나지 않도록 보장하는 방법</p>
<h2 id="61-매직값을-반환하지-말아야-한다">6.1 매직값을 반환하지 말아야 한다.</h2>
<p>매직 값은 함수의 정상적인 반환 유형에 적합하지만 특별한 의미를 가지고 있다. 매직값의 일반적인 예는 값이 없거나 오류가 발생했음을 나타내기 위해 -1을 반환하는 것이다.</p>
<h3 id="611-매직값은-버그를-유발할-수-있다">6.1.1 매직값은 버그를 유발할 수 있다</h3>
<blockquote>
<p>매직값(Magic Value)이란, 특정 값을 사용하여 의미를 부여하는 것으로, 그 값이 특정한 의미를 가지는 것을 의미한다.</p>
</blockquote>
<p>아래의 예시를 통해 알아보자.</p>
<pre><code class="language-java">class User {
    private Integer age;
    public Integer getAge(){
        if(age == null){
            return -1;
        }
        return age;
    };
}</code></pre>
<p>User클래스안에 사용자의 나이를 담는 변수 <code>age</code>가 존재하고 <code>getAge</code> 함수는 사용자의 나이가 입력되어있지 않을경우 null 대신 -1을 return 한다고 가정한다.
여기서 -1은 나이가 입력되어 있지 않다는 뜻을 가진 매직 값(Magic Value)이다.</p>
<p>다른 개발자가 모든 사용자들의 평균 나이를 구하기 위해 <code>User</code>클래스의 <code>getAge</code>함수를 사용한다고 가정해보자.</p>
<pre><code class="language-java">public double calculateAverageAge(List&lt;User&gt; users){
    int totalAge = 0;
    for(User user : users){
        totalAge += user.getAge();
    }

    return (double) totalAge / (double) users.size();
}</code></pre>
<p>개발자가 평균나이를 구하기 위해 <code>getAge</code> 함수를 호출해서 <code>totalAge</code> 변수에 더하는 과정에서 나이가 존재하지 않을경우 -1를 더하기 때문에 버그를 발생시킬 수 있다.</p>
<h3 id="612-해결책-널-옵셔널-또는-오류를-반환하라">6.1.2 해결책: 널, 옵셔널 또는 오류를 반환하라</h3>
<p>함수에서 매직값을 반환할 때의 문제점은 호출하는 쪽에서 함수의 구현내용에 대해 잘 알아야 한다는 점이다. 어떤 개발자들은 구현내용을 확인하지 않거나 확인하고나서 잊어버린다. 이런 경우에는 예측을 벗어나는 일이 생길 수 있다.</p>
<ol>
<li>null을 반환하라<pre><code class="language-java">class User {
 private Integer age;
 public Integer getAge(){
     return age;
 };
}</code></pre>
</li>
<li>옵셔널을 반환하라.<pre><code class="language-java">class User {
 private Integer age;
 public Optional&lt;Integer&gt; getAge(){
     if(age == null){
         return Optional.empty();
     }
     return Optional.of(age);
 };
}</code></pre>
</li>
<li>오류를 반환하라.<pre><code class="language-java">class User {
 private Integer age;
 public Integer getAge(){
     if(age == null){
         throw new CustomException(&quot;나이가 존재하지 않습니다.&quot;);
     }
     return age;
 };
}</code></pre>
<h2 id="62-널-객체-패턴을-적절히-사용하라">6.2 널 객체 패턴을 적절히 사용하라</h2>
값을 얻을 수 없을 때 널값이나 빈 옵셔널을 반환하는 대신 널 객체 패턴을 사용할 수 있다. 널 객체 패턴을 사용하는 이유는 널값을 반환하는 대신 유효한 값이 반환되어 그 이후에 실행되는 로직에서 널값으로 인해 시스템에 피해가 가지 않도록 하기 위함이다. 
널 객체 패턴의 가장 간단한 형태는 빈 문자열이나 빈 리스트를 반환하는 것이다.</li>
</ol>
<h3 id="621-빈-컬렉션을-반환하면-코드가-개선될-수-있다">6.2.1 빈 컬렉션을 반환하면 코드가 개선될 수 있다.</h3>
<p>HtmlElement에서 <code>highlighted</code>라는 class가 존재 여부를 체크하는 함수이다.</p>
<h3 id="개선-전-코드">개선 전 코드</h3>
<pre><code class="language-java">public Set&lt;String&gt; getClassNames(HtmlElement element) {
    String attirbute = element.getAttribute(&quot;class&quot;);
    if (attribute == null) {
        return null;
    }
    return new Hashet&lt;&gt;(attribute.split(&quot; &quot;));
}

public boolean isElementHighlighted(HtmlElement element){
    Set&lt;String&gt; classNames = getClassNames(element);
    if (classNames == null){ // getClassNames에서 null을 반환할 수 있으므로 null 처리를 해줘야함
        return false;
    }
    return classNames.contains(&quot;highlighted&quot;);
}</code></pre>
<h3 id="개선-후-코드">개선 후 코드</h3>
<pre><code class="language-java">public Set&lt;String&gt; getClassNames(HtmlElement element){
    String attirbute = element.getAttribute(&quot;class&quot;);
    if (attribute == null){
        return Collections.emptySet();
    }
    return new HashSet&lt;&gt;(attribute.split(&quot; &quot;));
}

public boolean isElementHighlighted(HtmlElement element){
    Set&lt;String&gt; classNames = getClassNames(element);
    //getClassNames에서 빈 Set객체를 반환해주니 null 처리를 할 필요가 없음
    return classNames.contains(&quot;highlighted&quot;); 
}</code></pre>
<h3 id="622-빈-문자열을-반환하는-것도-때로는-문제가-될-수-있다">6.2.2 빈 문자열을 반환하는 것도 때로는 문제가 될 수 있다.</h3>
<p>문자열이 문자들을 모아 놓은 것에 지나지 않으며, 이 경우 널 대신 빈 문자열을 반환하는 것이 적절할 수 있다. 문자열이 이것을 넘어서는 의미를 지닐 때, 널 대신 빈 문자열을 반환하는 것이 문제가 될 수 있다.</p>
<h3 id="623-더-복잡한-널-객체는-예측을-벗어날-수-있다">6.2.3 더 복잡한 널 객체는 예측을 벗어날 수 있다.</h3>
<p>함수를 호출할 때 널 객체 패턴을 사용하는 것은 본질적으로 빈 상자를 파는 것과 같다. 호출하는 쪽에 서 빈 상자를 받고 놀라거나 황당해할 가능성이 있다면, 널 객체 패턴을 피하는 것이 가장 좋을 것이다.</p>
<h2 id="63-예상치-못한-부수-효과를-피하라">6.3 예상치 못한 부수 효과를 피하라</h2>
<blockquote>
<p><code>부수 효과 (side effect)</code>는 어떤 함수의 호출로 인해 함수 외부에 초래한 상태 변화를 의미한다. 함수가 반환하는 값 외에 다른 효과가 있다면 이는 부수 효과가 있는 것이다.</p>
</blockquote>
<p>부수 효과를 일으키는 것을 피하는 방법</p>
<ul>
<li>클래스 및 함수를 불변(Immutable)으로 만든다.</li>
<li>호출하는 쪽에서 부수효과를 인지할 수 있도록 한다.</li>
</ul>
<h3 id="631-분명하고-의도적인-부수-효과는-괜찮다">6.3.1 분명하고 의도적인 부수 효과는 괜찮다.</h3>
<p>부수 효과가 코드의 어떤 부분에서 필요할 때가 있다.</p>
<pre><code class="language-java">class UserDisplay {
  private final Canvas canvas;
  ...

  void displayErrorMessage(String message) {
    canvas.drawText(message, Color.RED);
  }
}</code></pre>
<p><code>displayErrorMessage()</code> 함수는 한가지 부수 효과를 가지고 있는데, 사용자에게 표시되는 캔버스를 업데이트하는 것이다. 그러나 클래스를 <code>UserDisplay</code>라고 명명하고 함수를 <code>displayErrorMessage()</code>라고 하는 경우 이러한 부수 효과가 발생할 것이라는 점이 명백하고 예상을 벗어나는 동작은 없다.</p>
<h3 id="632-예기치-않은-부수-효과는-문제가-될-수-있다">6.3.2 예기치 않은 부수 효과는 문제가 될 수 있다.</h3>
<pre><code class="language-java">class UserDisplay {
  private final Canvas canvas;
  ...

  Color getPixel(Int x, Int y) {
    canvas.redraw();    // 다시 그리기 이벤트를 발생하는 것은 부수 효과다.
    PixelData data = canvas.getPixel(x, y);
    return new Color(
      data.getRed(),
      data.getGreen(),
      data.getBlue());
  }
}</code></pre>
<p>만약 canvas.redraw() 함수의 비용이 많이 들경우 이 부수효과를 알지 못하고 getPixel() 함수를 호출한 쪽은 큰 버그로 이어질수도 있다.</p>
<h3 id="633-해결책-부수-효과를-피하거나-그-사실을-분명하게-하라">6.3.3 해결책: 부수 효과를 피하거나 그 사실을 분명하게 하라</h3>
<ol>
<li>함수의 이름을 좀 더 명확하게 수정한다.<pre><code class="language-java">class UserDisplay {
private final Canvas canvas;
...
Color redrawAndGetPixel(Int x, Int y) { //함수명을 수정함으로써 부수효과가 있다는 것을 나타내고있다.
 canvas.redraw();
 PixelData data = canvas.getPixel(x, y);
 return new Color(
   data.getRed(),
   data.getGreen(),
   data.getBlue());
}
}</code></pre>
</li>
</ol>
<h2 id="64-입력-매개변수를-수정하는-것에-주의하라">6.4 입력 매개변수를 수정하는 것에 주의하라</h2>
<h3 id="641-입력-매개변수를-수정하면-버그를-초래할-수-있다">6.4.1 입력 매개변수를 수정하면 버그를 초래할 수 있다.</h3>
<p>만약 친구에게 책을 빌려준 후에 돌려받았는데 몇 페이지나 찢어져 있고 여백에 메모가 휘갈겨져 있다면 아마 화가 많이 날 것이다. 어떤 객체를 다른 함수에 입력으로 넘기는 것은 이처럼 친구에게 책을 빌려주는 것과 유사하다. 
입력으로 받은 객체에는 그 함수에 필요한 정보가 있지만, 이 함수가 호출된 뒤에도 해당 객체를 다른 용도로 사용할 가능성이 크다. 입력 매개변수를 수정하는 것은 함수가 외부의 무언가에 영향을 미치기 때문에 부수 효과의 또 다른 예다.</p>
<h3 id="642-해결책--변경하기-전에-복사하라">6.4.2 해결책 : 변경하기 전에 복사하라.</h3>
<pre><code class="language-java">List&lt;Invoice&gt; getBillableInvoices(Map&lt;User, Invoice&gt; userInvoices, Set&lt;User&gt; usersWithFreeTrial) {
  return userInvoices.entries()
                      .filter(entry -&gt;
                      !usersWithFreeTrial.contains(entry.getKey()))
                      .map(entry -&gt; entry.getValue());
}</code></pre>
<p>값을 복사하면 메모리나 CPU, 혹은 두 가지 모두와 관련해 성능에 영향을 미칠 수 있다. 하지만 입력 매개변수의 변경으로 인해 발생할 수 있는 예기치 못한 동작이나 버그와 비교하면 성능이 크게 문제되지 않는 경우가 많다.</p>
<h2 id="65-오해를-일으키는-함수는-작성하지-말라">6.5 오해를 일으키는 함수는 작성하지 말라</h2>
<p>개발자가 어떤 함수를 호출하는 코드를 접하면 그들은 자신들이 보는 것에 기초하여 함수 내부에서 무슨 일이 일어나고 있는지에 대해 생각한다. 하지만 항상 그렇지만은 않다면, 이 함수로 인해 예상을 벗어나는 결과와 버그로 이어지기 쉽다.</p>
<h3 id="651-중요한-입력이-누락되었을-때-아무것도-하지-않으면-놀랄-수-있다">6.5.1 중요한 입력이 누락되었을 때 아무것도 하지 않으면 놀랄 수 있다.</h3>
<p>매개변수가 없더라도 호출할 수 있고 해당 매개변수가 없으면 아무 작업도 수행하지 않는 함수가 있다면, 이 함수가 수행하는 작업에 대해 오해의 소지가 있을 수 있다.
호출하는 쪽에서는 해당 매개변수의 값을 제공하지 않고 함수를 호출하는 것의 심각성을 모를 수 있으며, 코드를 읽는 사람은 함수 호출 시 항상 무언가 작업이 이루어진다고 잘못 생각할 수 있다.</p>
<h3 id="652-해결책-중요한-입력은-필수-항목으로-만들라">6.5.2 해결책: 중요한 입력은 필수 항목으로 만들라</h3>
<p>어떤 매개변수 없이는 함수가 수행하려는 작업을 못 하는 경우 그 매개변수는 해당 함수에 중요하다. 이러한 매개변수에 대해서는 값을 사용할 수 없는 경우 함수를 호출할 수 없도록 널을 허용하지 않는 것이 더 안전할 수 있다.</p>
<pre><code class="language-java">public class Calculator {
    public int add(Integer a, Integer b){ // 매개변수가 잘못 들어왔을 경우 exception을 발생시킨다.
        if(a == null || b == null){
            throw new IllegAlargumentexception();
        }
        return a + b;
    }
}</code></pre>
<h2 id="66-미래를-대비한-열거형-처리">6.6 미래를 대비한 열거형 처리</h2>
<p>열거형을 처리해야 하는 경우 나중에 열거형에 더 많은 값이 추가될 수 있다는 점을 기억하는 것이 중요하다. 이것을 무시하고 코드를 작성하면, 자기 자신 혹은 다른 개발자들의 예측을 벗어나는 좋지 않은 결과를 초래할 수 있다.</p>
<h3 id="661-미래에-추가될-수-있는-열것값을-암묵적으로-처리하는-것은-문제가-될-수-있다">6.6.1 미래에 추가될 수 있는 열것값을 암묵적으로 처리하는 것은 문제가 될 수 있다.</h3>
<pre><code class="language-java">enum PredictedOutcome {
    COMPANY_WILL_GO_BUST,
    COMPANY_WILL_MAKE_A_PROFIT,
  }

  Boolean isOutcomeSafe(PredictedOutcome prediction) {
    if (prediction == PredictedOutcome.COMPANY_WILL_GO_BUST) {
    return false;
  }
  return true;
}</code></pre>
<p>만약 enum에 새로운 값이 추가될 경우 <code>isOutcomeSafe</code>의 반환값은 항상 true일 것 이다.
<code>isOutcomeSafe()</code> 함수 정의는 열거형 정의에서 수백 줄 떨어진 코드이거나, 완전히 다른 파일 혹은 다른 패키지에 있을 수 있다. 이 함수는 완전히 다른 팀이 유지보수할 수도 있다. 따라서 어떤 개발자가 <code>PredictedOutcome</code>에 값을 추가하면 그에 따라 <code>isOutcomeSafe()</code> 함수도 당연히 수정할 것이라고 가정하는 것은 위험하다.</p>
<h3 id="662-해결책-모든-경우를-처리하는-스위치-문을-사용하라">6.6.2 해결책: 모든 경우를 처리하는 스위치 문을 사용하라.</h3>
<pre><code class="language-java">enum PredictedOutcome {
  COMPANY_WILL_GO_BUST,
  COMPANY_WILL_MAKE_A_PROFIT,
}

Boolean isOutcomeSafe(PredictedOutcome prediction) {
  switch (prediction) {
    case COMPANY_WILL_GO_BUST:
    return false;
    case COMPANY_WILL_MAKE_A_PROFIT:
    return true;
  }
  throw new UncheckedException(&quot;Unhandled prediction: &quot; + prediction);
}</code></pre>
<p>이제 enum의 새로운 값이 추가된 후 isOutcomeSafe를 호출해도 예외가 발생해 개발자에게 알려줄 수 있다.</p>
<h3 id="663-기본-케이스를-주의하라">6.6.3 기본 케이스를 주의하라</h3>
<p>스위치 문은 일반적으로 처리되지 않은 모든 값에 대해 적용할 수 있는 기본 default 케이스를 지원한다. 열거형을 처리하는 스위치 문에 기본 케이스를 추가하면 향후 열거형 값이 암시적으로 처리될 수 있으며 잠재적으로 예기치 않은 문제와 버그가 발생할 수 있다.</p>
<pre><code class="language-java">Boolean isOutcomeSafe(PredictedOutcome prediction) {
  switch (prediction) {
    case COMPANY_WILL_GO_BUST:
    return false;
    case COMPANY_WILL_MAKE_A_PROFIT:
    return true;
    default:
    return false;
  }
}</code></pre>
<h2 id="67-이-모든-것을-테스트로-해결할-수는-없는가">6.7 이 모든 것을 테스트로 해결할 수는 없는가?</h2>
<ul>
<li><p>어떤 개발자들은 테스트에 대해 그다지 부지런하지 않을 수도 있다. 즉, 여러분의 코드에 대해 자신이 가정한 것이 틀렸다는 것을 드러내기 위해 충분한 시나리오나 코너 케이스를 테스트하지 않는다. 특정 시나리오 혹은 매우 큰 입력에서만 문제가 드러나는 경우 이런 상황이 가능하다.</p>
</li>
<li><p>테스트가 항상 실제 상황을 정확하게 시뮬레이션하는 것은 아니다. 코드를 테스트하는 개발자는 의존 라이브러리를 mock 객체를 통해 테스트해야 할 수도 있다. 이 경우 mock 객체가 어떻게 행동할 것인지 자신이 생각하는 바대로 프로그래밍한다. 실제 코드가 개발자의 가정과 예측을 벗어나는 방식으로 동작하지만 개발자가 이를 깨닫지 못한다면, mock 객체 자체를 올바르게 프로그래밍하지 못한다. 이런 일이 일어난다면 예상을 벗어나는 동작 때문에 발생하는 버그는 테스트를 통해 찾아내기가 어렵다.</p>
</li>
<li><p>어떤 것들은 테스트하기가 매우 어렵다. 멀티스레딩 문제와 관련된 버그는 종종 낮은 확률로 발생하고, 어느 정도 큰 규모에서 실행될 때만 나타날 수 있어서 테스트하기 어렵기로 악명 높다.</p>
</li>
</ul>
<p>테스트는 매우 중요하다. 아무리 많은 코드 구조화나 코드 계약에 대한 걱정도 고품질의 철저한 테스트를 대체할 수 없다. 그러나 그 반대 역시 사실이다. 직관적이지 않거나 예상을 벗어나는 코드에 숨어 있는 오류를 테스트만으로는 방지하기 어렵다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[왜 private메소드에는 @Transactional 어노테이션이 동작하지 않을까?]]></title>
            <link>https://velog.io/@w-beom/%EC%99%9C-private%EB%A9%94%EC%86%8C%EB%93%9C%EC%97%90%EB%8A%94-Transactional-%EC%96%B4%EB%85%B8%ED%85%8C%EC%9D%B4%EC%85%98%EC%9D%B4-%EB%8F%99%EC%9E%91%ED%95%98%EC%A7%80-%EC%95%8A%EC%9D%84%EA%B9%8C</link>
            <guid>https://velog.io/@w-beom/%EC%99%9C-private%EB%A9%94%EC%86%8C%EB%93%9C%EC%97%90%EB%8A%94-Transactional-%EC%96%B4%EB%85%B8%ED%85%8C%EC%9D%B4%EC%85%98%EC%9D%B4-%EB%8F%99%EC%9E%91%ED%95%98%EC%A7%80-%EC%95%8A%EC%9D%84%EA%B9%8C</guid>
            <pubDate>Sat, 04 Feb 2023 08:24:20 GMT</pubDate>
            <description><![CDATA[<h1 id="왜-private메소드에는-transactional-어노테이션이-동작하지-않을까">왜 private메소드에는 @Transactional 어노테이션이 동작하지 않을까?</h1>
<p>코드를 보다보면 메소드위에 <code>@Transactional</code>어노테이션이 붙어있는 것을 자주 볼 수 있습니다. 해당 어노테이션이 붙어있는 메소드는 트랜잭션이 걸린다는 것만 대략적으로 알고만 있었습니다.</p>
<p>어느날 문득 <code>@Transactional</code> 어노테이션이 붙어있는 메소드 아래 빨간줄이 떠있는것을 본적이 있었습니다.</p>
<p><img src="https://velog.velcdn.com/images/w-beom/post/6c230158-87cf-42f1-b666-00bec3291984/image.png" alt=""></p>
<p>메시지는 한글로 번역해 보니 <strong><code>&#39;@Transactional&#39; 어노테이션이 달린 메서드는 override 할 수 있어야 합니다.</code></strong> 라는 메세지였습니다. 처음에는 이 메시지를 봐도 무슨 소리인지 이해를 할 수가 없었습니다. 이해를 못하니 그냥 넘어가고 의심없이 트랜잭션이 정상적으로 동작한다고 생각하였습니다.</p>
<p>하지만 빨간줄이 뜨는 이유는 있었고 트랜잭션이 정상적으로 작동하지 않았는데, 그 이유에 대해서는 최근에 알게 되었습니다. </p>
<p>우선 결론만 말하자면 <code>@Transactional</code> Proxy형태로 동작하기 때문에 외부에서 접근 가능한 메소드만 <code>@Transactional</code> 설정이 가능합니다. </p>
<h2 id="transactional">@Transactional</h2>
<p><code>@Transactional</code> 어노테이션은 스프링 프레임워크에서 제공하는 트랜잭션 처리방법입니다. 사용방법은 트랜잭션을 적용하고 싶은 메소드 위에 <code>@Transactional</code> 어노테이션을 작성하는 것이 일반적이고, 이를 <code>선언적 트랜잭션</code>이라고 부릅니다.</p>
<p>단, 트랜잭션을 적용하기 위해 어노테이션을 붙이기 전에 주의해야할 것이 있는데 <code>@Transactional</code> 어노테이션을 메소드에 적용할 경우 <code>public</code> 메소드에만 적용할 수 있습니다. 이 이유에 대해서는 스프링에서 트랜잭션을 구현한 방법에 대해서 알아야합니다. 구현한 방법을 알아보기 전에 몇 가지 개념에 대해서 알고가야합니다.</p>
<h2 id="aop">AOP</h2>
<p>AOP는 Aspect Oriented Programming의 약자로 <code>관점 지향 프로그래밍</code>이라고 불립니다. 관점 지향은 쉽게 말해 <code>어떤 로직을 기준으로 핵심적인 관점, 부가적인 관점으로 나누어서 보고 그 관점을 기준으로 각각 모듈화 하겠다는 것</code>입니다. 여기서 모듈화란 어떤 공통된 로직이나 기능을 하나의 단위로 묶어내는 것을 말합니다.</p>
<p>생과일쥬스를 만드는 방법을 예시로 들어보겠습니다. 생과일 쥬스를 만들기 위해서는 아래와 같은 방법(로직)으로 행동합니다.</p>
<ol>
<li>컵을 준비한다.</li>
<li>컵에 얼음을 넣는다.</li>
<li>오렌지를 착즙한다.</li>
<li>빨대를 꽂는다.</li>
</ol>
<p>위의 방법을 그림으로 표현하면 아래와 같습니다.</p>
<p><img src="https://velog.velcdn.com/images/w-beom/post/675ef693-fde8-4e19-8fe3-924bfa1a9b38/image.png" alt=""></p>
<p>만약, 오렌지쥬스가 아닌 포도쥬스, 사과쥬스 등등 다른 과일쥬스를 마시고 싶을 때는 어떻게 해야할까요? 아래 그림과 같이 동작해야 할 것입니다.</p>
<p><img src="https://velog.velcdn.com/images/w-beom/post/6138bdff-cca9-4652-8f31-8d7ba9333ca4/image.png" alt=""></p>
<p>여기서 불편한 점이 생기게 됩니다. 다른 과일쥬스를 마시기 위해서는 1번, 2번, 4번의 과정을 다시 반복해야 한다는 것 입니다. </p>
<p>AOP 개념을 적용해보면 <code>생과일쥬스를 만드는 로직을 기준으로 핵심적인 관점(쥬스를 착즙하는 과정)과 부가적인 관점(컵,얼음,빨대를 준비하는 과정)</code>으로 나누어 관점을 기준으로 나눌 수 있습니다. 이렇게 핵심적인 기능이 아닌 중간중간 삽입되어야 할 기능(관심)들을 <code>횡단관심사</code>라고 합니다.</p>
<p><img src="https://velog.velcdn.com/images/w-beom/post/dc1db1cd-9ee7-4bd3-b45a-ea66948e96c4/image.png" alt=""></p>
<h2 id="spring-aop">Spring AOP</h2>
<p>AOP를 구현하는 방법에는 3가지가 있습니다.</p>
<ul>
<li>컴파일 시점에 코드에 공통 기능을 삽입하는 방법</li>
<li>클래스 로딩 시점에 바이트 코드에 공통 기능을 삽입하는 방법</li>
<li>런타임에 프록시 객체를 생성해서 공통 기능을 삽입하는 방법 (프록시 패턴)</li>
</ul>
<p>Spring AOP는 위 방법중 3번째 방법인 프록시 패턴을 이용해 AOP를 구현하고 있습니다.</p>
<h2 id="프록시-패턴">프록시 패턴</h2>
<p>프록시(Proxy)를 번역하면 대리자, 대변인의 의미를 갖고 있습니다. 대리자, 대변인은 누군가를 대신해서 그 역할을 수행하는 존재입니다. 이는 프로그램에서 똑같이 적용되는데, 프록시 객체에게 어떤 일을 대신 시키는 것 입니다.</p>
<p>다시 한번 생과일쥬스를 만드는 것으로 예를 들어보겠습니다. 먼저 프록시 패턴을 적용하기전의 코드입니다.</p>
<pre><code class="language-java">public interface JuiceMaker {
    void makeJuice();
}

// 사과쥬스 
public class AppleJuiceMaker implements JuiceMaker {
    public void makeJuice() {
        System.out.println(&quot;컵을 준비합니다.&quot;);
        System.out.println(&quot;컵에 얼음을 담습니다.&quot;);
        System.out.println(&quot;사과를 착즙합니다.&quot;);
        System.out.println(&quot;컵에 빨대를 꽂습니다.&quot;);
    }
}

// 포도쥬스
public class GrapeJuiceMaker implements JuiceMaker{
    public void makeJuice() {
        System.out.println(&quot;컵을 준비합니다.&quot;);
        System.out.println(&quot;컵에 얼음을 담습니다.&quot;);
        System.out.println(&quot;포도를 착즙합니다.&quot;);
        System.out.println(&quot;컵에 빨대를 꽂습니다.&quot;);
    }
}

// 오렌지쥬스
public class OrangeJuiceMaker implements JuiceMaker {
    public void makeJuice() {
        System.out.println(&quot;컵을 준비합니다.&quot;);
        System.out.println(&quot;컵에 얼음을 담습니다.&quot;);
        System.out.println(&quot;오렌지를 착즙합니다.&quot;);
        System.out.println(&quot;컵에 빨대를 꽂습니다.&quot;);
    }
}

public class Main {
    public static void main(String[] args) {
        JuiceMaker appleJuiceMaker = new AppleJuiceMaker();
        appleJuiceMaker.makeJuice();

        JuiceMaker grapeJuiceMaker = new GrapeJuiceMaker();
        grapeJuiceMaker.makeJuice();
    }
}

// 실행결과

컵을 준비합니다.
컵에 얼음을 담습니다.
사과를 착즙합니다.
컵에 빨대를 꽂습니다.

컵을 준비합니다.
컵에 얼음을 담습니다.
포도를 착즙합니다.
컵에 빨대를 꽂습니다.</code></pre>
<p>구현하고 싶은 쥬스 제조기 클래스를 <code>JuiceMaker</code> 인터페이스를 구현한 뒤 1번~4번의 과정을 구현합니다.</p>
<p>만약에 쥬스를 생성하는 로직에서 컵에 얼음을 담으면 쥬스가 밍밍해진다고 얼음을 빼고 쥬스를 만든다고 하면 사과, 오렌지, 포도쥬스 제조기의 메소드를 모두 수정해야 할 것입니다.</p>
<p>현재는 3개의 클래스의 메소드만 수정해도 되지만 만약에 더 많은 과일들이 있고 그 개수가 100개 1000개라면 수정하는 상상만해도 끔찍할 것 같습니다.</p>
<p>여기서 AOP개념을 도입해 프록시패턴을 이용해 구현해 보겠습니다. 우선 <code>핵심 관점</code> 과 <code>부가적인 관점</code> 을 구분지어야 합니다. </p>
<ul>
<li><strong>핵심관점 : 과일을 착즙하는 일</strong></li>
<li><strong>부가적인 관점 : 횡단관심사인 컵, 얼음, 빨대를 준비하는 일</strong></li>
</ul>
<p>그리고 실제 객체의 일을 대신해줄 프록시 객체가 존재해야합니다.</p>
<pre><code class="language-java">public interface JuiceMaker {
    void makeJuice();
}
// =========== 핵심관점==========
// 사과쥬스 
public class AppleJuiceMaker implements JuiceMaker {
    public void makeJuice() {
        System.out.println(&quot;사과를 착즙합니다.&quot;);
    }
}

// 포도쥬스
public class GrapeJuiceMaker implements JuiceMaker{
    public void makeJuice() {
        System.out.println(&quot;포도를 착즙합니다.&quot;);
    }
}

// 오렌지쥬스
public class OrangeJuiceMaker implements JuiceMaker {
    public void makeJuice() {
        System.out.println(&quot;오렌지를 착즙합니다.&quot;);
    }
}

//============ 부가적인 관점 (프록시 객체) ============

// 프록시 객체
public class JuiceMakerProxy implements JuiceMaker {
    private JuiceMaker juiceMaker;

    public JuiceMakerProxy(JuiceMaker juiceMaker) {
        this.juiceMaker = juiceMaker;
    }

    public void before() {
        System.out.println(&quot;컵을 준비합니다.&quot;);
        System.out.println(&quot;컵에 얼음을 담습니다.&quot;);
    }

    public void after() {
        System.out.println(&quot;컵에 빨대를 꽂습니다.&quot;);
    }

    @Override
    public void makeJuice() {
        this.before();
        juiceMaker.makeJuice();
        this.after();
    }
}

// 클라이언트
public class Main {
    public static void main(String[] args) {
                // 프록시 객체를 생성한다.
        JuiceMaker juiceMakerProxy = new JuiceMakerProxy(new AppleJuiceMaker());
        juiceMakerProxy.makeJuice();

        System.out.println();

        juiceMakerProxy = new JuiceMakerProxy(new GrapeJuiceMaker());
        juiceMakerProxy.makeJuice();
    }
}

// 실행결과  위의 코드와 실행결과가 같다.
컵을 준비합니다.
컵에 얼음을 담습니다.
사과를 착즙합니다.
컵에 빨대를 꽂습니다.

컵을 준비합니다.
컵에 얼음을 담습니다.
포도를 착즙합니다.
컵에 빨대를 꽂습니다.</code></pre>
<p><code>JuiceMakerProxy</code> 프록시 객체는 <code>JuiceMaker</code> 인터페이스를 구현하면서 필드로 <code>JuiceMaker</code> 를 가져야합니다. 필드로 가지게 되는 <code>JuiceMaker</code> 의 일을 대신 해야하기 때문입니다. </p>
<p>여기서 이 글의 주제가 나오게 됩니다. 
Spring AOP는 프록시패턴을 이용해 구현합니다. <code>@Transactional</code> 어노테이션은 스프링Bean으로 등록된 객체에 한하여 프록시 객체를 생성한 뒤 <code>@Transactional</code> 어노테이션이 붙은 메소드를 실행을 시킵니다. 
만약 위의 코드에서 <code>makeJuice()</code>메소드가 <code>private</code> 메소드라면 프록시 객체가 필드로 갖고 있는 <code>JuiceMaker</code>객체의  메소드를 실행할 수 없어 트랜잭션이 정상적으로 작동하지 않는 것 입니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[2년차 개발자가 처음 써보는 회고록 - 2021]]></title>
            <link>https://velog.io/@w-beom/1.5%EB%85%84%EC%B0%A8-%EA%B0%9C%EB%B0%9C%EC%9E%90%EA%B0%80-%EC%B2%98%EC%9D%8C-%EC%8D%A8%EB%B3%B4%EB%8A%94-%ED%9A%8C%EA%B3%A0%EB%A1%9D-2021</link>
            <guid>https://velog.io/@w-beom/1.5%EB%85%84%EC%B0%A8-%EA%B0%9C%EB%B0%9C%EC%9E%90%EA%B0%80-%EC%B2%98%EC%9D%8C-%EC%8D%A8%EB%B3%B4%EB%8A%94-%ED%9A%8C%EA%B3%A0%EB%A1%9D-2021</guid>
            <pubDate>Sat, 01 Jan 2022 08:15:31 GMT</pubDate>
            <description><![CDATA[<p>사실 2020년에도 한 번 회고록을 써보려고 했었는데 다른 사람들의 퀄리티 높은 회고록들을 보면서 나도 저렇게 글을 써볼 수 있을까 하며 지레 겁먹으며 그냥 없었던 일 것처럼 넘어갔다. 올해는 그런 두려움에서 벗어나고자 나도 한번 회고록을 작성해보려고 한다!</p>
<h2 id="📝첫-스터디">📝첫 스터디</h2>
<p><strong>무지함이 두려웠다.</strong> 나의 무지함이 두려워서 스터디를 시작한 것이 아니라 나의 무지함이 스터디를 하면서 들통날까봐 두려웠다. 그래서 다른 사람이 스터디를 같이 하자고 제안이 왔을때도 바쁘다는 핑계로 거절했었다. 그런 두려움에 빠져 있을 시절, 평소에 내가 활동하고있는 개발자 오픈채팅방에 계시는 어떤 한 분이 좋은 말씀을 해주셨다.</p>
<blockquote>
<p>스터디라는게 서로 모르는 것을 배우고 잘 아는것은 공유하며 서로 성장해가는 것이 목적이다. 내가 모르는 것을 부끄러워 하지 말고 남에게 배울줄 아는 태도를 가져야 하고 잘 아는것은 남에게 공유해주는 것이 좋은 자세다. 내가 잘 알고 있는건 내가 잘해서가 아니라 남들 보다 먼저 해보았기 때문이다.</p>
</blockquote>
<p>그 분은 지나가는 말로 했던 것일수도 있지만 당시 내 상황에 잘 맞아떨어지는 것인지 몰라도 무엇인가 가슴 깊속을 후벼파는 말이었다.</p>
<p><strong>2021년은 달라져야했다.</strong> 첫 스터디는 사내 스터디로 진행해보고자 마음을 먹었다.
솔직히 말하자면 지금 회사는 스터디 문화, 공유하는 문화가 있지 않다. 그래서 더욱 더 사내스터디를 진행하면서 스터디, 공유하는 문화를 만들어 보고 싶은 까닭에 첫 스터디로 사내스터디를 진행해보고자 하는 마음이 컸다.</p>
<h3 id="스터디-시작">스터디 시작</h3>
<p><img src="https://images.velog.io/images/w-beom/post/aae08cf8-e8de-4e65-81e3-8bf378590578/image.png" alt=""></p>
<p><strong>사내 스터디인 만큼 나와 동료들이 성장하면서도 회사내에서도 적용해볼 수 있는 주제가 필요했다.</strong> 주제를 정하는데는 그리 오래 걸리지 않았다. 회사에서는 자바8을 사용하지만 회사 소스코드를 보고있자하면 모던하지 않았다.  우리는 조금 더 모던(?)해질 필요가 있었기에 <code>모던 자바 인 액션</code>이라는 책을 주제로 정했다. <del>(사실 자바17이 나온 현재는 8도 모던하지 않다..)</del></p>
<p>진행방식은 한 주동안 정해진 분량까지 각자 책을 읽고 github 저장소에 자유형식으로 정리한 뒤 일주일에 한 번 모임을 가져 발표하는 형식으로 진행하였다.</p>
<p>결론부터 말하자면 선방했다. 함께 스터디를 진행했던 분들이 열정이 있었고 나 또한 그 열정에 힘입어 열심히 할 수 있었다. 또, 모르는 것을 부끄러워 하지않고 질문할 수 있는 용기를 가지게 됐다.</p>
<p>선방이라고 했던 이유는 중간에 한 분이 파견으로 인해  더 이상 참여를 할 수 없어서 세 명에서 두 명으로 바뀌기도 하였고 진행방식에 대해 단점이 있었다. 세 명으로 진행하는 스터디 였는데도 불구하고 한 주마다 세 명이 모두 발표를 진행하니 예상했던 시간보다 지체됐다. 또, 앞 사람이 발표한 내용과 뒷 사람이 발표한 내용이 중복돼 스터디가 지루해지는 경향이 있었다.
이러한 단점들을 교훈으로 삼고 추후 진행했던 사내스터디는 삼색펜 공부법 이라는 방식으로 진행하였다. </p>
<p><a href="https://github.com/w-beom/modern-java-study">모던 자바 인 액션 스터디 저장소</a></p>
<h2 id="다음-스터디-그리고">다음 스터디 그리고</h2>
<p>첫 스터디를 무사히 마무리 하고 잠깐 한 숨을 돌릴까도 했지만 그럴 틈이 없었다. 개발자 오픈채팅방에서 활동하시는 분들이 스터디 멤버를 모집했는데 지금 이 기회가 아니면 왠지 그 분들과 스터디를 할 수 있는 날이 없을 것 같았다. </p>
<h3 id="스터디-괌--스터디-쿠바">스터디 괌 &amp; 스터디 쿠바</h3>
<p><img src="https://images.velog.io/images/w-beom/post/69254f81-0550-4ba8-98ac-7f29eff64f50/image.png" alt=""></p>
<p><img src="https://images.velog.io/images/w-beom/post/5160e039-ac0d-419a-aa8e-59917241b83a/image.png" alt=""></p>
<p>온라인으로 처음 진행한 스터디, 인프라의 &#39;인&#39;자도  모르는 내가 그냥 무지성으로 시작한 스터디다. 매 주 스터디를 진행할 때 마다 도전하는 느낌이였지만 천사같은 스터디원분 들의 보살핌과 도움으로 무사히(?) 마칠 수 있는 스터디였다. gcp, k8s, docker, DDD 관련 지식을 공부했지만 지식 뿐만 아니라 해당 기술의 기본적인 원리, 공식 문서를 통한 공부 방법 등도 깨달을 수 있는 스터디였다.</p>
<p><a href="https://github.com/w-beom/study-guam">스터디 괌 저장소</a>
<a href="https://github.com/w-beom/study-kuba">스터디 쿠바 저장소</a></p>
<h3 id="오브젝트-클린-아키텍처-소프트웨어-아키텍처-101">오브젝트, 클린 아키텍처, 소프트웨어 아키텍처 101</h3>
<p>이 스터디 또한 개발자 오픈채팅방에서 시작이 됐다. 좋은 분들과 함께 지금까지 약 1년동안 계속 이어져 오고 있는 스터디다. 오브젝트라는 책으로 스터디를 시작할 때만 해도 스터디원 중 한 분이 우스갯소리로 &quot;책을 다 읽었을 때 쯤 우리가 쌩깔지도 모르지만 열심히 해봐요.&quot; 라고 하셨었는데 다행히(?) 지금까지 인연을 이어오고 있다. 다들 열정이 넘치고 개발을 좋아해서 지금까지 인연을 이어왔을 수 있던 것 같다.</p>
<p><img src="https://images.velog.io/images/w-beom/post/e636eff1-9c7f-4b1e-a77b-da0478deaed2/image.png" alt=""></p>
<p><img src="https://images.velog.io/images/w-beom/post/d4a4e13d-f6fc-4bc7-8cbc-aed3c46fc09e/image.png" alt=""></p>
<p>매 주 수요일마다 구글밋을 통해 스터디를 진행하는데 현재는 소프트웨어 아키텍처 101이라는 책으로 진행중이며 위에서 잠깐 언급했던 삼색펜 공부법이라는 방식을 채택하고 있다. 책도 읽고 공부도 하고 스터디원분들이 나보다 경험이 많으시니 사담으로 듣는 내용도 재밌고 유용했다. </p>
<p><img src="https://images.velog.io/images/w-beom/post/d6c6ddfc-5c7e-40d9-b9a4-b7e1c17e06b2/image.png" alt=""></p>
<p><img src="https://images.velog.io/images/w-beom/post/cc9e8345-3676-4290-b805-4a0fbd2dda39/image.png" alt=""></p>
<p>최근에는 스터디장님이 1년간 스터디를 함께 해줘서 고맙다고 신년선물겸 스터디원분들 각각에게 책을 1권씩 선물해주셨다. 생각지도 못한 선물을 받아서 너무 너무 기뻤다. 어쩌면 난 인복이 타고난 걸 수도..?</p>
<p><img src="https://images.velog.io/images/w-beom/post/331d5f92-668d-4405-9ff2-0b7197bcd1c4/image.png" alt=""></p>
<p><img src="https://images.velog.io/images/w-beom/post/05390cc6-a647-46a9-affe-fb52b4e88bb1/image.png" alt=""></p>
<p>클린 아키텍처로 스터디를 진행할 때 경험이 없다보니 이해가 안되거나 피부로 와닿지 않는 부분이 많았는데 최근에 나온 <code>만들면서 배우는 클린 아키텍처</code>라는 책을 통해 그 부족한 부분을 메꿀수있을 것 같아서 이 책으로 선택했다. 시간 될 때 조금씩 봐야겠다.</p>
<h3 id="친절한-sql튜닝">친절한 SQL튜닝</h3>
<p>두번째로 진행한 사내 스터디다. (현재 진행중)</p>
<p>그룹웨어 시장 경쟁에서 이길려면 고객에게 그룹웨어 솔루션을 판매하여야한다. 판매하려면 영업을 해야하고 영업을 하려면 사용자가 기대하는 기대치를 만족시킬 수 있는 서비스가 있어야 한다. 결국 계속해서 새로운 기능을 만들어 낼 수 밖에 없고 무수히 쏟아지는 요구사항을 정신없이 찍어내다보면 개발을 하고 있는 내가 아니라 무지성으로 코드를 찍어내는 나를 발견하였다. 당연히 점점 유지보수 하기가 힘들어지고 조금만 복잡한 쿼리를 태우면 성능이 안 좋았다. 성능을 개선하기 위해서는 다른 부분도 있을테지만 슬로우 쿼리를 잡아내서 개선을 시키는 것이 우선순위라고 생각했다. 그래서 회사분들에게 스터디를 하자고 졸랐다. 다행히 다들 관심이 있었는지 재밌을 것 같다고 무려 나 포함 6명이나 스터디를 시작하게 됐다.</p>
<p><img src="https://images.velog.io/images/w-beom/post/4ea2f0e3-7eb1-481a-b5bb-3041420e17c0/image.png" alt=""></p>
<p><img src="https://images.velog.io/images/w-beom/post/e16bd499-59eb-4d82-95d7-254fac0c23b4/image.png" alt=""></p>
<p>현재 진행중인 스터디므로 아직 회사에 적용시켜본적은 없지만 내가 여태까지 얼마나 무지성으로 SQL을 작성하고 DB에 대한 이해도가 떨어졌는지 반성할 수 있는 시간이고 스터디가 마무리 될 때까지 쭉 그럴 것 같다. 반성하는 만큼 얻는 것도 많을거라 생각하고 얼른 마무리하고 회사에 한번 적용해보고 싶은 부푼 꿈이 있다.</p>
<h2 id="🛒nextstep-tdd-clean-code-with-java-11기">🛒NEXTSTEP (TDD, Clean Code with Java 11기)</h2>
<p>NEXTSTEP에서 진행하는 TDD, Clean Code 교육에 참여하게 됐다. 적은 비용은 아니였지만 교육내용이 좋다고 소문이 나기도 했고 내가 성장하려면 나에게 재투자를 해야한다고 생각했다.</p>
<p>박재성님이 말씀해주시는 흔한 SI의 코드들, 레거시 코드를 리팩토링 해야하는 이유에 적극적으로 공감하기도 했고 객체지향, TDD, Clean Code 등등 내가 추상적으로 알고 있던 것에 대해 조금이나마 다가갈 수 있었다. <del>(그럼에도 아직 객체지향은 어렵다)</del></p>
<h3 id="코드리뷰">코드리뷰</h3>
<p>NEXTSTEP은 강의뿐만 아니라 과제를 기반으로 진행을 하게 되는데 내가 제출한 코드를 보며 리뷰어분들이 코드리뷰를 해주신다. 무지성으로 작성한 부끄러운 내 코드를 누군가 본다는게 민망하기도 했고 어떤 리뷰를 해줄까 하는 설렘도 있었다.</p>
<p><strong>코드리뷰의 중요성을 깨달았다.</strong> 사람은 직접 겪어봐야 깨닫는다고 했던가? 내가 직접 코드리뷰를 당해보니 너무 좋았다. 내가 코드를 어떻게 작성해야되는지에 대한 방향성도 잡혔고 무엇보다 내 똥같은 질문을 찰떡같이 알아들어서 리뷰를 해주는게 좋았다...</p>
<p><img src="https://images.velog.io/images/w-beom/post/d7ad78a5-24ca-4c40-b210-5b902212c674/image.png" alt=""></p>
<p><img src="https://images.velog.io/images/w-beom/post/9a0246fb-df9d-47f7-b14c-7cc4781ac4a0/image.png" alt=""></p>
<p><img src="https://images.velog.io/images/w-beom/post/4a041e9d-04c9-45f6-9cbc-9712dac7d9a6/image.png" alt=""></p>
<p><img src="https://images.velog.io/images/w-beom/post/f48fbac9-707a-46d6-93aa-0e55ac880987/image.png" alt=""></p>
<p><img src="https://images.velog.io/images/w-beom/post/0c1d5112-d5c5-4469-b1dd-9e8fb18acff7/image.png" alt=""></p>
<p>결과적으로 수료를 하진 못했다. 핑계지만 NEXTSTEP 과정을 진행하면서 중간쯤 회사일도 너무 바빠지기 시작하더니 결국 미루다미루다 보니 4-1까지 통과 후 제출하지 못하고 있다. 박재성님이 NEXTSTEP 기간이 끝나도 리뷰만 요청하면 대신 리뷰를 해줄테니 꼭 포기하지말고 수료를 할 수 있으면 좋겠다고 하셨으니 2022년에는 수료를 도전해보고 싶다.</p>
<h2 id="👀google-foo-bar-challenge">👀Google Foo Bar Challenge</h2>
<p><img src="https://images.velog.io/images/w-beom/post/f2220457-3d1f-4fe9-a769-6f9af199622a/image.png" alt=""></p>
<p><img src="https://images.velog.io/images/w-beom/post/234b3783-ca87-44e7-a37f-c757f632236d/image.png" alt=""></p>
<p>평소와 다름없이 구글에 검색을 하고 있었는데 검색 결과 화면은 평소의 내가 보던 화면이 아니었다.</p>
<p><code>Curios develops are known to seek interesting problems. Solve one from Google?</code></p>
<p>순간 &quot;이게 뭐지?&quot; 라는 생각과 함께 무심코 화면을 꺼버리면 안될 것 같았다. 곧 바로 새로운 크롬창을 키고 검은화면에 있는 문구로 검색을 해보았다. 그 결과는 아래와 같았다.</p>
<p>
  <img src="https://images.velog.io/images/w-beom/post/ce44ad77-348c-49d4-a33b-0d6b4a92db23/image.png">
</p>

<p><img src="https://images.velog.io/images/w-beom/post/b7c44c03-5b88-456a-9b47-671883bcfaab/image.png" alt=""></p>
<p><a href="https://patataeater.blogspot.com/2020/08/how-to-get-hired-by-google.html">Everything You Need to Know About Google Foobar Challenge</a></p>
<p>이름은 <code>Google Foo Bar Challenge</code> 구글에서 이스터에그(?)로 심어 놓은 비밀 채용 프로세스다. 구글에 프로그래밍 관련 검색을 하다보면 정말 우연히 구글 검색화면이 갈라지면서 위와 같은 화면이 보여지게 된다고 한다. 알고리즘 문제들이 단계별로 구성되어있는데</p>
<ul>
<li>LEVEL1 - 문제 1개</li>
<li>LEVEL2 - 문제 2개</li>
<li>LEVEL3 - 문제 3개</li>
<li>LEVEL4 - 문제 2개</li>
<li>LEVEL5 - 문제 1개</li>
</ul>
<p>위와 같은 구성으로 문제가 준비되어있다. LEVEL3까지 풀게 되면 인터뷰를 위한 개인정보를 제출하라는 메세지가 나오고 제출을 하고 나면 그 이후에는 구글측에서 인터뷰 제안이 온다고 한다. </p>
<p><img src="https://images.velog.io/images/w-beom/post/d6bb2b17-4ca6-42a0-876c-e2c302b5cf90/image.png" alt=""></p>
<p><img src="https://images.velog.io/images/w-beom/post/44808195-7ccd-43f6-bf68-e3f563f0c1af/image.png" alt=""></p>
<p>1단계는 생각보다 쉬웠다. 단계를 통과할 때마다 토끼가 나와서 양옆으로 껑충껑충 뛰어다닌다. 1단계를 통과하니 한 마리의 토끼가 뛰어다녔다. 순간 머릿속에서 행복회로를 수만번 돌리기 시작했다. 2단계로 건너뛰니 난이도가 갑자기 올라가기 시작했다. 결국 2-2단계까지 풀고 2-3단계에서 끝내 제출하지 못했다.</p>
<blockquote>
<p>준비된 사람만이 기회를 잡는다.</p>
</blockquote>
<p>여태까지 살면서 수 많은 기회가 스쳐지나갔을 수도 있다. 다만 내가 준비되지 않았던 사람이라 그 기회가 기회인지도 몰랐을 수도 있고 Foo Bar Challenge처럼 알면서도 기회를 놓치는 경우가 있다. 나는 기회가 와도 준비가 되지 않은 사람이라 기회를 잡지 못했다. 이번 기회는 잡지 못한게 아쉽지만 다른 좋은 기회가 왔을 때 그 때는 꼭 기회를 잡는 사람이 되야겠다는 자기 반성 시간을 가지게 됐다. 어쩌면 구글신이 자기반성을 한번 해보라는 선물이 아니였을까.... 재미있는 추억이 생긴 것 같아서 마냥 아쉽지만은 아니었던 에피소드였다.</p>
<h2 id="마치며">마치며</h2>
<p>처음으로 회고록을 써봤는데 뒤돌아보니 나름 2021년을 나쁘지 않게 보낸 것 같다. 글로 정리하니 어떤 점이 좋았고 어떤 점이 아쉬웠는지 또 그 때를 되새기게 되면서 내가 당시에 무슨 생각을 했었는지 돌아볼 수 있는 시간이였다.</p>
<p>2021년은 이론적으로 스터디의 초점이 맞춰졌다면 올해는 개발을 좀 해야할 것 같다. 책을 통해서 이론을 공부하는 것도 좋았으나 특히 아키텍처쪽을 공부할 때는 경험이 거의 없다시피하니 피부에 와닿지 않는 부분들도 많았다. 올해는 꼭 하나의 서비스를 만들어보며 이론적으로 배웠던 것 들을 적용시키고 시행착오를 겪어보는 것이 하나의 목표다.</p>
<p>시간이 너무 빠르게 흘러가는 것 같다. 벌써 20대의 마지막을 바라보게 됐고 경력은 3년차를 눈 앞에 두고있다. 가끔은 경력이 쌓이는게 두렵기도 하다. 경력이 쌓이면 경력만큼의 기대치를 충족해야하기 때문이기도 하고 주변을 둘러보면 같은 년차 또는 나보다 경력이 낮으신 분들 이지만 실력이 월등하게 뛰어나시는 분들도 많다. 결론은 더 열심히 해야겠다 ㅠㅠ</p>
<p>나이를 먹다 보니 건강이 안 좋아지기 시작한게 느껴진다. 불규칙적인 식습관이 원인인 것 같기도 하지만 나는 항상 야식을 좋아했고 식습관은 바뀌지 않았는데 나이가 바뀌어서 그런가보다. 체력도 점점 안 좋아지는 것 같고 최근엔 태어나서 처음으로 위염을 진단 받았다. 이제 나도 건강식품 먹고 몸 관리를 해야할 것 같다.</p>
<p>글을 잘 못써서 어떻게 마무리해야 될지 모르겠다. 2022년은 내가 사랑하고 아끼는 사람들은 모두 잘 됐으면 좋겠고 나도 잘 됐으면 좋겠다!!!!!!!!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[모던 자바 인 액션 Chapter 1]]></title>
            <link>https://velog.io/@w-beom/%EB%AA%A8%EB%8D%98-%EC%9E%90%EB%B0%94-%EC%9D%B8-%EC%95%A1%EC%85%98-Chapter-1</link>
            <guid>https://velog.io/@w-beom/%EB%AA%A8%EB%8D%98-%EC%9E%90%EB%B0%94-%EC%9D%B8-%EC%95%A1%EC%85%98-Chapter-1</guid>
            <pubDate>Sun, 24 Jan 2021 14:56:46 GMT</pubDate>
            <description><![CDATA[<h1 id="자바-8-9-10-11-무슨-일이-일어나고-있는가">자바 8, 9, 10, 11 무슨 일이 일어나고 있는가?</h1>
<h2 id="자연어에-가까운-코드">자연어에 가까운 코드</h2>
<p>자바 역사를 통틀어 가장 큰 변화가 자바 8에서 일어났다. 예를 들어 다음은 사과 목록을 무게순으로 정렬하는 고전적 코드다.</p>
<pre><code class="language-java">Collections.sort(inventory, new Comparator&lt;Apple&gt;() {
    public int compare(Apple a1, Apple a2){
        return a1.getWeight().compareTo(a2.getWeight);
    }
});</code></pre>
<p>자바 8을 이용하면 자연어에 더 가깝게 간단한 방식으로 코드를 구현할 수 있다.</p>
<pre><code class="language-java">inventory.sort(comparing(Apple::getWeight));</code></pre>
<h2 id="자바-8에서-제공하는-새로운-기술">자바 8에서 제공하는 새로운 기술</h2>
<ul>
<li>스트림 API</li>
<li>메서드에 코드를 전달하는 기법</li>
<li>인터페이스의 디폴트 메서드</li>
</ul>
<h3 id="1-스트림-api">1. 스트림 API</h3>
<p>거의 모든 자바 애플리케이션은 <code>Collection</code> 을 만들고 활용한다. 하지만 <code>Collection</code>으로 모든 문제가 해결되는 것은 아니다. </p>
<p>아래의 예제 코드를 보도록 하자.</p>
<pre><code class="language-java">//리스트에서 고가의 거래만 필터링을 한다음 통화로 결과를 그룹화하는 코드이다.

Map&lt;Currency, List&lt;Transaction&gt;&gt; transactionsByCurrencies = new HasMap&lt;&gt;();
for (Transaction transaction : transactions){
    if (transaction.getPrice() &gt; 1000){
        Currency currency = transaction.getCurrenct();
        List&lt;Transaction&gt; transactionsForCurrency = transactionsByCurrencies.get(currency);
        if(transactionsForCurrenc == null){
            transactionsForCurrency = new ArrayList&lt;&gt;();
            transactionsByCurrencies.put(currency, transactionsForCurrency);
        }
        transactionsForCurrency.add(transaction);
    }
}</code></pre>
<p>스트림 API를 이용하면 다음처럴 코드를 작성할 수 있다.</p>
<pre><code class="language-java">Map&lt;Currency, List&lt;Transaction&gt;&gt; transactionsByCurrencies = transactions.stream()
                .filter((Transaction t) -&gt; t.getPrice() &gt; 1000)
                .collect(groupingBy(Transaction::getCurrency));</code></pre>
<ul>
<li>자바 8에는 <code>java.util.stream</code> 패키지에 스트림 API가 추가되었다. 스트림 패키지에 정의된 Stream<T>는 T 형식으로 구성된 일련의 항목을 의미한다.</li>
<li>스트림 API의 핵심은 기존에는 한 번에 한 항목을 처리했지만 자바 8에서는 우리가 하려는 작업을 고수준으로 추상화해서 일련의 스트림으로 만들어 처리할 수 있다.</li>
<li>파이프라인을 이용해서 입력 부분을 여러 CPU 코어에 쉽게 할당할 수 있다는 부가적인 이득도 얻을 수 있다.</li>
<li>스레드라는 복잡한 작업을 사용하지 않으면서도 <strong>공짜</strong>로 병렬성을 얻을 수 있다.</li>
</ul>
<h3 id="2-메서드에-코드를-전달하는-기법">2. 메서드에 코드를 전달하는 기법</h3>
<ul>
<li>코드 일부를 API로 전달하는 기능이다.</li>
<li>메서드에 코드를 전달하는 기법을 이용하면 새롭고 간결한 방식으로 <code>동작 파라미터화</code>를 구현할 수 있다.</li>
</ul>
<h3 id="3-인터페이스의-디폴트-메서드">3. 인터페이스의 디폴트 메서드</h3>
<blockquote>
<p>😮 디폴트 메서드의 탄생배경</p>
</blockquote>
<pre><code class="language-java">//inventory에서 Apple의 무게가 150이 넘는 Apple객체만 필터링하는 예제
List&lt;Apple&gt; heavyApples1 = inventory.stream()
        .filter((Apple a) -&gt; a.getWeight() &gt; 150)
        .collect(toList());</code></pre>
<p>자바 8 이전에는 <code>List&lt;T&gt;</code> 가<code>stream</code> 메서드를 지원하지 않는다는 것이 문제다.</p>
<p>따라서 위 예제는 컴파일 할 수 없는 코드다. 가장 간단한 해결책은 직접 인터페이스를 만들어 <code>Collection</code>인터페이스에 <code>stream</code>메서드를 추가하고 <code>ArrayList</code> 클래스에서 메서드를 구현하는 것이다.</p>
<p>하지만 이 방법은 사용자에게 너무 큰 고통을 안겨준다. 이미 <code>Collection</code>API의 인터페이스를 구현하는 많은 <code>Collection</code>프레임워크가 존재한다. 인터페이스에 새로운 메서드를 추가한다면 인터페이스를 구현하는 모든 클래스는 새로 추가된 메서드를 구현해야 한다. </p>
<blockquote>
<p>🙄여기서 우리는 딜레마에 빠진다. 어떻게 기존의 구현을 고치지 않고도 이미 공개된 인터페이스를 변경할 수 있을까?</p>
</blockquote>
<p>자바 8에서는 구현 클래스에서 구현하지 않아도 되는 메서드를 인터페이스에 추가할 수 있는 기능을 제공한다. 메서드 본문은 클래스 구현이 아니라 인터페이스의 일부로 포함된다. 그래서 이를 디폴트 메서드 <code>default method</code>라 부른다.</p>
<p>아래의 코드는 <code>List</code>인터페이스에 추가된 <code>default method</code>이다.</p>
<pre><code class="language-java">default void sort(Comparator&lt;? super E&gt; c) {
    Object[] a = this.toArray();
    Arrays.sort(a, (Comparator) c);
    ListIterator&lt;E&gt; i = this.listIterator();
    for (Object e : a) {
        i.next();
        i.set((E) e);
    }
}</code></pre>
<h2 id="일급-객체first-class-citizen란">일급 객체(First Class Citizen)란?</h2>
<p>일급 객체는 아래 3개의 조건을 충족하는 객체를 1급객체라고 정의할 수 있다.</p>
<ol>
<li>변수와 데이터에 할당 할 수 있어야 한다.</li>
<li>객체의 인자로 넘길 수 있어야 한다.</li>
<li>객체의 리턴값으로 리턴 할 수 있어야 한다.</li>
</ol>
<h3 id="🙄-java에서-메서드는-일급-객체인지-알아보자">🙄 JAVA에서 메서드는 일급 객체인지 알아보자</h3>
<pre><code class="language-java">// name을 가져오는 메서드가 하나 존재한다.
public String getName(){
  //TODO
}

// 조건1. 변수와 데이터에 할당 할 수 있어야 한다.
var name = getName;// Error # 변수에 메서드를 할당할 수 없다.

// 조건2. 객체의 인자로 넘길 수 있어야한다.
findByName(getName); // Error # 메서드의 인자로 메서드를 넘길 수 없다.

// 조건3. 객체의 리턴값으로 리턴 할 수 있어야 한다.
public doSomething() {
   return getName; //Error #return 값으로 메서드를 넘길 수 없다
}

//결론 JAVA에서 메소드는 일급객체가 아니다.</code></pre>
<p>만약 런타임 시점에 메서드를 전달할 수 있다면, 즉 메서드를 일급 객체로 만들면 프로그래밍에 유용하게 활용할 수 있다. 따라서 자바 8 설계자들은 이급 객체를 일급 객체로 바꿀수 있는 기능을 추가했다.</p>
<h2 id="메서드와-람다를-일급객체로">메서드와 람다를 일급객체로</h2>
<p>메서드를 일급객체로 사용하면 프로그래머가 활용할 수 있는 도구가 다양해지면서 프로그래밍이 수월해진다는 사실을 이미 실험을 통해 확인했다. 
그래서 자바 8의 설계자들은 메서드를 값으로 취급할 수 있게, 프로그래머들이 더 쉽게 프로그램을 구현할 수 있는 환경이 제공되도록 자바8을 설계하기로 결정했다.</p>
<h3 id="메서드를-일급객체로-사용하기-위한-새로운-기능">메서드를 일급객체로 사용하기 위한 새로운 기능</h3>
<ol>
<li><p>메소드 참조 (method reference)</p>
<p> 디렉터리에서 모든 숨겨진 파일을 필터링한다고 가정하자. 
 우선 주어진 파일이 숨겨져 있는지 여부를 알려주는 메서드를 구현해야 한다. 
 다행히 File클래스는 이미 isHidden 메서드를 제공한다.</p>
</li>
</ol>
<pre><code class="language-java">    File[] hiddenFiles = new File(&quot;.&quot;).listFiles(new FileFilter() {
        @Override
        public boolean accept(File file) {
            return file.isHidden(); // &lt;- 숨겨진 파일 필터링!
        }
    });</code></pre>
<p>그런데 완성한 코드가 마음에 들지 않는다. 단 세 행의 코드지만 각 행이 무슨 작업을 하는지 투명하지 않다.
<code>File</code> 클래스에는 이미 <code>isHidden</code>이라는 메서드가 있는데 왜 굳이 <code>FileFilter</code>로 <code>isHidden</code>을 복잡하게 감싼다음에 <code>FileFilter</code>를 인스턴스화 해야할까? 자바 8이 나타나기 전까지는 달리 방법이 없었기 때문이다.</p>
<pre><code>자바 8에서는 아래와 같이 코드를 구현할 수 있다.</code></pre><pre><code class="language-java">    File[] hiddenFiles = new File(&quot;.&quot;).listFiles(File::isHidden);</code></pre>
<pre><code>이미 isHidden이라는 함수는 준비되어 있으므로 자바 8의 `메서드 참조 ::`  를 이용해서 listFiles에 직접 전달할 수 있다.</code></pre><ol start="2">
<li><p>람다 : 익명함수</p>
<p> 자바 8에서는 메서드를 일급값으로 취급할 뿐 아니라 람다를 포함하여 함수도 값으로 취급할 수 있다.</p>
</li>
</ol>
<ul>
<li>코드를 넘겨주는 예제를 살펴보며 메서드 레퍼런스와 람다에 대해 좀 더 알아보자</li>
</ul>
<pre><code class="language-java">//Apple 클래스와 getColor 메서드가 있고, Apples 리스트를 포함하는 변수 inventory가 있다.
//이때 모든 녹색 사과를 선택해서 리스트를 반환하는 프로그램을 구현하려한다.

public static List&lt;Apple&gt; filterGreenApples (List &lt; Apple &gt; inventory) {
    List&lt;Apple&gt; result = new ArrayList&lt;&gt;(); // 반환되는 result는 List로, 처음에는 비어있지만 
                                                                                        //점점 녹색사과로 채워진다.

    for (Apple apple : inventory){
        if(GREEN.equals(apple.getColor())){ // 녹색사과만 선택한다.
            result.add(apple);
        }
    }
    return result;
}</code></pre>
<p>하지만 누군가는 사과를 무게로 필터링하고 싶을 수 있다. 그러면 우리는 아래와 같이 코드를 구현할 수 있을 것이다.</p>
<pre><code class="language-java">public static List&lt;Apple&gt; filterHeavyApples (List &lt; Apple &gt; inventory) {
    List&lt;Apple&gt; result = new ArrayList&lt;&gt;(); 

    for (Apple apple : inventory){
        if(apple.getWeight() &gt; 150){ // 150을 초과하는 무게를 가진 사과만 선택한다.
            result.add(apple);
        }
    }
    return result;
}</code></pre>
<p>위 두개의 메서드는 if문만 다르다</p>
<p>만약 두 메서드가 단순히 크기를 기준으로 사과를 필터링하는 상황이었다면 인수로 (150,1000)을 넘겨주어 150그램 이상의 사과를 선택하거나 (0,80)을 넘겨주어 80그램 이하의 사과를 선택할 수 있을 것이다.</p>
<p>자바 8에서는 코드를 인수로 넘겨줄 수 있으므로 filter 메서드를 중복으로 구현할 필요가 없다.</p>
<p>앞의 코드를 다음처럼 자바 8에 맞게 구현할 수 있다.</p>
<pre><code class="language-java">public static boolean isGreenApple(Apple apple){
    return GREEN.equals(apple.getColor());
}

public static boolean isHeavyApple(Apple apple){
    return apple.getWeight() &gt; 150;
}

public interface Predicate&lt;T&gt;{
    boolean test(T t);
}

static List&lt;Apple&gt; filterApples(List&lt;Apple&gt; inventroy, Predicate&lt;Apple&gt; p){
    List&lt;Apple&gt; result = new ArrayList&lt;&gt;();
    for(Apple apple : inventory){
        if(p.test(apple)){
            result.add(apple);
        }
    }
    return result;
}

//다음처럼 메서드를 호출할 수 있다.
filterApples(inventory, Apple::isGreenApple);

//또는 다음과 같이 호출해도 된다.
filterApples(inventory, Apple::isHeavyApple);</code></pre>
<p>위와 같이 메서드를 전달하는 것은 유용한 기능이나 isHeavyApple, isGreenApple처럼 한 두번만 사용할 메서드를 매번 정의하는 것은 귀찮은 일이다. 자바 8에서는 이 문제도 간단히 해결할 수 있다.</p>
<pre><code class="language-java">filterApples(inventory, (Apple a) -&gt; GREEN.equals(a.getColor()));

//또는 다음과 같이 구현한다.
filterApples(inventory, (Apple a) -&gt; a.getWeight() &gt; 150);

//심지어 다음과 같이 구현할 수도 있다.
filterApple(inventory, (Apple a) -&gt; a.getWeight() &lt; 80 || RED.equals(a.getColor()));</code></pre>
<p>즉, 한 번만 사용할 메서드는 따로 정의를 구현할 필요가 없다. 위 코드는 우리가 넘겨주려는 코드를 애써 찾을 필요가 없을 정도로 더 짧고 간결하다.</p>
<p>하지만 람다가 몇 줄 이상으로 길어진다면 익명 람다보다는 코드가 수행하는 일을 잘 설명하는 이름을 가진 메서드를 정의하고 메서드 참조를 활용 하는것이 바람직하다.</p>
<h2 id="마치며">마치며</h2>
<ul>
<li>함수는 일급값이다. 메서들르 어떻게 함수형값으로 넘겨주는지, 익명 함수(람다)를 어떻게 구현하는지 기억하자.</li>
<li>자바 8은 프로그램을 더 효과적이고 간결하게 구현할 수 있는 새로운 개념과 기능을 제공한다.</li>
<li>자바 8의 스트림 개념 중 일부는 컬렉션에서 가져온 것이다. 스트림과 컬렉션을 적절하게 활용하면 스트림의 인수를 병렬로 처리할 수 있으며 더 가독성이 좋은 코드를 구현할 수 있다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[30-Day LeetCoding Challenge - 10Day (Min Stack)]]></title>
            <link>https://velog.io/@w-beom/30-Day-LeetCoding-Challenge-10Day-Min-Stack</link>
            <guid>https://velog.io/@w-beom/30-Day-LeetCoding-Challenge-10Day-Min-Stack</guid>
            <pubDate>Sat, 11 Apr 2020 09:35:40 GMT</pubDate>
            <description><![CDATA[<h2 id="min-stack">Min Stack</h2>
<p>Design a stack that supports push, pop, top, and retrieving the minimum element in constant time.</p>
<p>push(x) -- Push element x onto stack.
pop() -- Removes the element on top of the stack.
top() -- Get the top element.
getMin() -- Retrieve the minimum element in the stack.</p>
<pre>
Example:

MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.getMin();   --> Returns -3.
minStack.pop();
minStack.top();      --> Returns 0.
minStack.getMin();   --> Returns -2.
</pre>

<ul>
<li>Stack을 구현해야 하는 문제이다.
push(x), pop(), top(), getMin() 메소드도 함께 구현해야한다. </li>
</ul>
<h2 id="solution">Solution</h2>
<pre><code class="language-java">import java.util.*;
class MinStack {

    /** initialize your data structure here. */
    List&lt;Integer&gt; stack;

    public MinStack() {
        stack = new ArrayList&lt;Integer&gt;();
    }

    public void push(int x) {
        stack.add(0, x);
    }

    public void pop() {
        stack.remove(0);
    }

    public int top() {
        return stack.get(0);    
    }

    public int getMin() {
        int min = Integer.MAX_VALUE;
        for (int i = 0; i &lt; stack.size(); i++) {
            if (min &gt; stack.get(i)) {
                min = stack.get(i);
            }
        }
        return min;
    }
}</code></pre>
<ul>
<li>문제에서 요구하는 MinStack을 ArrayList를 이용해 구현해 보았다.</li>
<li>MinStack 인스턴스를 생성하면 객체 할당이 된다.</li>
<li>push(int x)는 항상 ArrayList의 첫 번째 index로 삽입</li>
<li>Stack은 Last In First Out 이니 나중에 들어온 값이 첫 번째로 나가야한다. 
stack은 항상 첫 번째 요소로 삽입되니 마지막에 들어온 값은 항상 index가 0이다. 
그러므로 pop()은 항상 stack의 0번째 index를 삭제한다.</li>
<li>top()은 스택의 마지막에 들어온 값을 출력하는 메소드이다.</li>
<li>getMin()은 stack의 최솟값을 반환해주는 메소드이다.
나는 stack의 0번째 index부터 마지막 index까지 최솟값을 찾아서 반환해주게 작성하였다.</li>
</ul>
<h2 id="마침">마침</h2>
<p>문제를 풀고 다른 사람들이 작성한 코드를 보았는데 push()메소드를 작성할 때 값이 들어올 때 부터 최솟값을 비교하면서 최솟값을 가지고 있는 변수를 따로 생성해서 저장해놓으면 getMin()메소드에서 해당 변수를 바로 반환해주게 하는 방법도 있다는 것을 알았다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[30-Day LeetCoding Challenge - 9Day (Backspace String Compare)]]></title>
            <link>https://velog.io/@w-beom/30-Day-LeetCoding-Challenge-8Day-Backspace-String-Compare</link>
            <guid>https://velog.io/@w-beom/30-Day-LeetCoding-Challenge-8Day-Backspace-String-Compare</guid>
            <pubDate>Sat, 11 Apr 2020 08:18:38 GMT</pubDate>
            <description><![CDATA[<h2 id="backspace-string-compare">Backspace String Compare</h2>
<p>Given two strings S and T, return if they are equal when both are typed into empty text editors. # means a backspace character.</p>
<pre>
Example 1:

Input: S = "ab#c", T = "ad#c"
Output: true
Explanation: Both S and T become "ac".
</pre>
<pre>
Example 2:

Input: S = "ab##", T = "c#d#"
Output: true
Explanation: Both S and T become "".
</pre>
<pre>
Example 3:

Input: S = "a##c", T = "#a#c"
Output: true
Explanation: Both S and T become "c".
</pre>
<pre>
Example 4:

Input: S = "a#c", T = "b"
Output: false
Explanation: S becomes "c" while T becomes "b".
</pre>
<p>Note:</p>
<ul>
<li>1 &lt;= S.length &lt;= 200</li>
<li>1 &lt;= T.length &lt;= 200</li>
<li>S and T only contain lowercase letters and &#39;#&#39; characters.</li>
</ul>
<p>Follow up:</p>
<ul>
<li>Can you solve it in O(N) time and O(1) space?</li>
</ul>
<p>S와 T에 문자열이 주어지는데 #은 Backspace key를 의미한다.
즉 S= &quot;ab##&quot;이면 ab후 Backspace 2번 이니 S=&quot;&quot;가 남게되는 것 이다.
이럴 때 S와T가 같으면 true를 아니면 false를 반환하는 메소드를 작성하는 문제이다.</p>
<h2 id="solution">Solution</h2>
<pre><code class="language-java">class Solution {
    public boolean backspaceCompare(String S, String T) {
        Stack&lt;Character&gt; sStk = new Stack&lt;&gt;();
    Stack&lt;Character&gt; tStk = new Stack&lt;&gt;();

    for (int i = 0; i &lt; S.length(); i++) {
        if (S.charAt(i) == &#39;#&#39;) {
            if (sStk.size() &gt; 0)
                sStk.pop();
        } else {
            sStk.push(S.charAt(i));
        }
    }

    for (int i = 0; i &lt; T.length(); i++) {
        if (T.charAt(i) == &#39;#&#39;) {
            if (tStk.size() &gt; 0)
                tStk.pop();
        } else {
            tStk.push(T.charAt(i));
        }
    }

    if (sStk.equals(tStk))
        return true;
    else
        return false;
    }
}</code></pre>
<ul>
<li>Stack을 이용해 Stack의 크기가 0보다 크고 <sub>(값이 있으면)</sub> String S의 i번째 문자가 &#39;#&#39;이 아니면 pop()을 아니면 push()를 하여 Stack sStk와 tStk의 값이 같은지 비교를 한다.</li>
<li>같으면 true 다르면 false를 반환!</li>
</ul>
<h2 id="마침">마침</h2>
<p>문제를 풀고 답지를 봤는데 같은 방식인데 훨씬 간단하게 풀 수 있었다.... 계속 문제를 풀면서 비슷한 for문인데 어떻게 하면 더 효율적으로 짤 수 있을까 고민했었는데 생각이 안나서 그냥 제출했는데 답지를 보니깐 나는 빡머가리인가보다 ㅜㅜ</p>
<h3 id="leetcode-solution">Leetcode Solution</h3>
<pre><code class="language-java">class Solution {
    public boolean backspaceCompare(String S, String T) {
        return build(S).equals(build(T));
    }

    public String build(String S) {
        Stack&lt;Character&gt; ans = new Stack();
        for (char c: S.toCharArray()) {
            if (c != &#39;#&#39;)
                ans.push(c);
            else if (!ans.empty())
                ans.pop();
        }
        return String.valueOf(ans);
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[30-Day LeetCoding Challenge - 8Day (Middle of the Linked List)]]></title>
            <link>https://velog.io/@w-beom/30-Day-LeetCoding-Challenge-8Day-Middle-of-the-Linked-List</link>
            <guid>https://velog.io/@w-beom/30-Day-LeetCoding-Challenge-8Day-Middle-of-the-Linked-List</guid>
            <pubDate>Fri, 10 Apr 2020 17:58:30 GMT</pubDate>
            <description><![CDATA[<h2 id="middle-of-the-linked-list">Middle of the Linked List</h2>
<p>Given a non-empty, singly linked list with head node head, return a middle node of linked list.</p>
<p>If there are two middle nodes, return the second middle node.</p>
<pre>
Example 1:

Input: [1,2,3,4,5]
Output: Node 3 from this list (Serialization: [3,4,5])
The returned node has value 3.  (The judge's serialization of this node is [3,4,5]).
Note that we returned a ListNode object ans, such that:
ans.val = 3, ans.next.val = 4, ans.next.next.val = 5, and ans.next.next.next = NULL.
</pre>
<pre>
Example 2:

Input: [1,2,3,4,5,6]
Output: Node 4 from this list (Serialization: [4,5,6])
Since the list has two middle nodes with values 3 and 4, we return the second one.
</pre>


<p>Note:</p>
<ul>
<li>The number of nodes in the given list will be between 1 and 100.</li>
</ul>
<p>Input으로 Node가 주어질 때 가운데 있는 노드를 반환하는 문제이다.</p>
<h2 id="solution">Solution</h2>
<pre><code class="language-java">/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */</code></pre>
<p> ListNode 클래스는 위와 같이 구현되어있다.</p>
<pre><code class="language-java"> class Solution {
    public ListNode middleNode(ListNode head) {
    ListNode node = head;
    ListNode resultNode = head;
    while (resultNode != null &amp;&amp; resultNode.next != null) {
        node = node.next;
        resultNode = resultNode.next.next;
    }
    return node;
   }
}</code></pre>
<ul>
<li>파라미터로 들어오는 ListNode를 두 개의 새로운 ListNode로 대입한다.</li>
<li>node객체는 다음 노드로 한 칸씩 resultNode는 두 번씩 이동하면  resultNode가 끝에 다다랐을 땐 node객체는 절반만큼만 이동하게되서 최종적으로 middle node를 반환 할 수 있다!</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Java] HashMap의 getOfDefault() : 중복 갯수 확인]]></title>
            <link>https://velog.io/@w-beom/Java-HashMap%EC%9D%98-getOfDefault-%EC%A4%91%EB%B3%B5-%EA%B0%AF%EC%88%98-%ED%99%95%EC%9D%B8</link>
            <guid>https://velog.io/@w-beom/Java-HashMap%EC%9D%98-getOfDefault-%EC%A4%91%EB%B3%B5-%EA%B0%AF%EC%88%98-%ED%99%95%EC%9D%B8</guid>
            <pubDate>Wed, 08 Apr 2020 17:16:35 GMT</pubDate>
            <description><![CDATA[<p>프로그래머스 문제를 풀던 도중 효율성으로 인해서 합격이 되지 않는 문제가 생겼다.</p>
<p>그래서 다른 사람들의 코드들을 보면서 하나 배운게 있다.</p>
<p>HashMap의 getOfDefault() 이다.</p>
<pre><code class="language-java">Integer getOrDefault(Object key, Integer defaultValue)</code></pre>
<p>해당 key값이 존재하면 key의 value를 반환하고 존재하지 않으면 기본값을 반환한다.</p>
<p>이 메소드를 이용해서 배열에서 중복되는 값이 몇 개 있는지 확인할 수 있는 효율적인 알고리즘을 작성할 수 있다는걸 알았다.</p>
<h2 id="code">CODE</h2>
<pre><code class="language-java">import java.util.HashMap;
import java.util.Map;

public class Main {
    public static void main(String[] args) {
    String[] arr= {&quot;aaa&quot;,&quot;aaa&quot;,&quot;bbb&quot;,&quot;ccc&quot;,&quot;ddd&quot;,&quot;ccc&quot;,&quot;eee&quot;};

    Map&lt;String, Integer&gt; map = new HashMap&lt;String, Integer&gt;();
    for(String key : arr) {
        map.put(key, map.getOrDefault(key, 0)+1);
    }
    System.out.println(map);
    }
}

출력결과-----------------------------
{aaa=2, ccc=2, bbb=1, eee=1, ddd=1}
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[30-Day LeetCoding Challenge - 7Day (Counting Elements)]]></title>
            <link>https://velog.io/@w-beom/30-Day-LeetCoding-Challenge-7Day-Counting-Elements</link>
            <guid>https://velog.io/@w-beom/30-Day-LeetCoding-Challenge-7Day-Counting-Elements</guid>
            <pubDate>Wed, 08 Apr 2020 16:39:30 GMT</pubDate>
            <description><![CDATA[<h2 id="counting-elements">Counting Elements</h2>
<p>Given an integer array arr, count element x such that x + 1 is also in arr.</p>
<p>If there&#39;re duplicates in arr, count them seperately.</p>
<pre>
Example 1:

Input: arr = [1,2,3]
Output: 2
Explanation: 1 and 2 are counted cause 2 and 3 are in arr.
</pre>
<pre>
Example 2:

Input: arr = [1,1,3,3,5,5,7,7]
Output: 0
Explanation: No numbers are counted, cause there's no 2, 4, 6, or 8 in arr.
</pre>
<pre>
Example 3:

Input: arr = [1,3,2,3,5,0]
Output: 3
Explanation: 0, 1 and 2 are counted cause 1, 2 and 3 are in arr.
</pre>
<pre>
Example 4:

Input: arr = [1,1,2,2]
Output: 2
Explanation: Two 1s are counted cause 2 is in arr.
</pre>


<p>Constraints:</p>
<ul>
<li>1 &lt;= arr.length &lt;= 1000</li>
<li>0 &lt;= arr[i] &lt;= 1000</li>
</ul>
<h2 id="solution">Solution</h2>
<pre><code class="language-java">class Solution {
    public int countElements(int[] arr) {
    HashMap&lt;Integer, Integer&gt; eleMap = new HashMap&lt;&gt;();
    int count = 0;
    for (int num : arr) {
        eleMap.put(num, eleMap.getOrDefault(num, 0) + 1);
    }
    Iterator&lt;Integer&gt; keys = eleMap.keySet().iterator();
    while (keys.hasNext()) {
        int key = keys.next();
        if (eleMap.containsKey(key + 1)) {
            count+=eleMap.get(key);
        }
    }
    return count;
   }
}</code></pre>
<p>해당 배열의 i번째 x값이 있으면 x와 x+1이 짝을 이루어 몇 개 있는지 반환하는 메소드를 
작성하는 것이다.</p>
<ul>
<li><code>해당 Solution에서 최근에 알게된 HashMap 객체에 있는 getOrDefault()를     사용하였다. getOrDefault()를 이용해 배열에서 중복되는 값의 갯수를 쉽게 찾아낼 수 있다!</code><ul>
<li><code>getOrDefault(Object key, Integer defaultValue): 찾는 키가 존재하면 해당 키의 값을 반환, 존재하지 않으면 기본값을 반환한다.</code>
<code>getOrDefault()</code></li>
</ul>
</li>
</ul>
<ol>
<li><code>arr[] 배열로 값이 [1,1,2,2]가 들어온다고 가정하자</code></li>
<li><code>eleMap.put(num, eleMap.getOrDefault(num, 0) + 1); 구문이 실행 된 후 HashMap은 {1=2,2=2} 이런식으로 저장되어 있을 것이다.</code></li>
<li><code>Iterator를 이용해 eleMap의 key값을 모두 가져온다.</code></li>
<li><code>eleMap안에 key값으로 key+1이 존재하면 count에 해당 key의 value를 더한다.</code><ul>
<li>key=x라 할 때 key+1=x+1이기 때문에 key+1이 존재하는지 찾는 것!</li>
</ul>
</li>
<li><code>(4)번 과정을 마지막 key값까지 반복한 후 count를 반환한다!</code></li>
</ol>
<h2 id="마침">마침</h2>
<p>하루하루 챌린지를 하면서 확실히 뭔가를 배워가는 느낌이 들기 시작했다. ㅎㅎㅎ
원래 공부라는 게 자신이 성장했다는 것을 깨닫기 힘들어서 꾸준히 하기가 힘든데
벌써부터 뭔가를 알아간다는 걸 느끼는 게 다행이라 생각한다. 더 꾸준히 해야겠다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[30-Day LeetCoding Challenge - 6Day (Group Anagrams)]]></title>
            <link>https://velog.io/@w-beom/30-Day-LeetCoding-Challenge-6Day-Group-Anagrams</link>
            <guid>https://velog.io/@w-beom/30-Day-LeetCoding-Challenge-6Day-Group-Anagrams</guid>
            <pubDate>Wed, 08 Apr 2020 15:13:45 GMT</pubDate>
            <description><![CDATA[<h2 id="anagrams이란">Anagrams이란?</h2>
<blockquote>
<p>어구전철(語句轉綴) / Anagram
일종의 말장난으로 어떠한 단어의 문자를 재배열하여 다른 뜻을 가지는 다른 단어로 바꾸는 것을 말한다. 고대 유대인들이 히브리어로 하곤 했고, 중세 유럽에도 큰 인기를 끌었다. 프랑스 궁정에서는 &#39;왕을 위해 애너그램을 하는 사람&#39;을 고용하기도 했을 정도다. 중세의 대표적인 어구전철은 라틴어로 된 아베 마리아의 애너그램이다.</p>
</blockquote>
<p><a href="https://namu.wiki/w/%EC%95%A0%EB%84%88%EA%B7%B8%EB%9E%A8">애너그램[나무위키]</a></p>
<h2 id="group-anagrams">Group Anagrams</h2>
<p>Given an array of strings, group anagrams together.</p>
<pre>
Example:

Input: ["eat", "tea", "tan", "ate", "nat", "bat"],
Output:
[
  ["ate","eat","tea"],
  ["nat","tan"],
  ["bat"]
]
</pre>
<p>Note:</p>
<ul>
<li>All inputs will be in lowercase.</li>
<li>The order of your output does not matter.</li>
</ul>
<h2 id="solution">Solution</h2>
<pre><code class="language-java">class Solution {
    public List&lt;List&lt;String&gt;&gt; groupAnagrams(String[] strs) {
        Map&lt;String, List&gt; hashStr = new HashMap&lt;String,List&gt;();
    for (String str : strs) {
        char[] arr = str.toCharArray();
        Arrays.sort(arr);
        String key = String.valueOf(arr);
        if (!hashStr.containsKey(key)) {
            hashStr.put(key, new ArrayList());
        }
        hashStr.get(key).add(str);
    }
    return new ArrayList(hashStr.values());
   }
}</code></pre>
<ol>
<li><code>strs[]</code> 배열에 <code>[&quot;eat&quot;, &quot;tea&quot;, &quot;tan&quot;, &quot;ate&quot;, &quot;nat&quot;, &quot;bat&quot;]</code> 가 들어온다고 가정하자</li>
<li><code>strs[]</code> 배열에서 첫 번째 인덱스의 문자열부터 <code>char[]</code> 배열에 담은 뒤 정렬한다.<ul>
<li>정렬을 하게되면 <code>[&quot;aet&quot;,&quot;aet&quot;,&quot;ant&quot;,&quot;aet&quot;,&quot;ant&quot;,&quot;abt&quot;]</code> 이런식으로 앞에서 하나씩 정렬될 것이다.</li>
</ul>
</li>
<li>정렬한 배열을 <code>valueOf()</code>메소드를 이용해 <code>String</code>으로 변환한 뒤 해당 <code>String</code> 객체를 키 값으로 사용할 것 이다.</li>
<li>만약 <code>hashStr</code>객체에 <code>key</code>값으로 <code>key</code>값이 존재하지 않으면 <code>hashMap</code>에 <code>해당 key와 값을 put 한다.</code></li>
<li><code>for문이 끝나면 새로운 ArrayList를 반환한다.</code></li>
</ol>
<h2 id="마침">마침</h2>
<p>이번 문제에서 가장 핵심 포인트는 strs[]배열의 값들을 char[] 배열로 변환한 뒤 정렬을 하고 해당 값들을 key값으로 사용했다는 것이다. </p>
<p>요즘 면접 보러 다니느라 하루 이틀 밀리기 시작했다.. 밀려도 꾸준히 해야지</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[30-Day LeetCoding Challenge - 5Day (Best Time to Buy and Sell Stock II)]]></title>
            <link>https://velog.io/@w-beom/30-Day-LeetCoding-Challenge-5Day-Best-Time-to-Buy-and-Sell-Stock-II</link>
            <guid>https://velog.io/@w-beom/30-Day-LeetCoding-Challenge-5Day-Best-Time-to-Buy-and-Sell-Stock-II</guid>
            <pubDate>Sun, 05 Apr 2020 08:13:04 GMT</pubDate>
            <description><![CDATA[<h2 id="best-time-to-buy-and-sell-stock-ii">Best Time to Buy and Sell Stock II</h2>
<p>Say you have an array for which the ith element is the price of a given stock on day i.</p>
<p>Design an algorithm to find the maximum profit. You may complete as many transactions as you like (i.e., buy one and sell one share of the stock multiple times).</p>
<p>Note: You may not engage in multiple transactions at the same time (i.e., you must sell the stock before you buy again).</p>
<pre>
Example 1:

Input: [7,1,5,3,6,4]
Output: 7
Explanation: Buy on day 2 (price = 1) and sell on day 3 (price = 5), profit = 5-1 = 4.
         Then buy on day 4 (price = 3) and sell on day 5 (price = 6), profit = 6-3 = 3.


Example 2:

Input: [1,2,3,4,5]
Output: 4
Explanation: Buy on day 1 (price = 1) and sell on day 5 (price = 5), profit = 5-1 = 4.
             Note that you cannot buy on day 1, buy on day 2 and sell them later, as you are
             engaging multiple transactions at the same time. You must sell before buying again.


Example 3:

Input: [7,6,4,3,1]
Output: 0
Explanation: In this case, no transaction is done, i.e. max profit = 0.
</pre>

<p>주어진 배열에는 i번째 일에 살 수 있는 주식의 가격이 들어있다. 
주식은 여러번 사고 팔 수 있으며 주식을 사거나 팔려면 내가 갖고있던 주식을 팔고 다시 사거나 해야한다. 중복으로 거래할 순 없다. 
이렇게 했을때 최대 이익을 낼 수 있는 값을 도출해내는 문제이다.</p>
<ul>
<li><p>Example1</p>
<ul>
<li>2일 날 1의 가격으로 주식 구매</li>
<li>3일 날 5의 가격으로 주식 판매, 이익 5-1 = 4</li>
<li>4일 날 3의 가격으로 주식 구매</li>
<li>5일 날 6의 가격으로 주식 판매, 이익 6-3 = 3
총 이익 4+3 = 7</li>
</ul>
</li>
<li><p>Exmaple2</p>
<ul>
<li>1일 날 1의 가격으로 주식 구매</li>
<li>5일 날 5의 가격으로 주식 판매
총 이익 5-1 = 4</li>
</ul>
</li>
<li><p>Exmaple3</p>
<ul>
<li>이익을 낼 수 없으므로 0 리턴</li>
</ul>
</li>
</ul>
<h2 id="soultion">Soultion</h2>
<pre><code class="language-java">class Solution {
    public int maxProfit(int[] prices) {
        int result = 0;
    for (int i = 0; i &lt; prices.length - 1; i++) {
        if (prices[i + 1] &gt; prices[i]) {
            result += (prices[i + 1] - prices[i]);
        }
    }
    return result;
   }
}</code></pre>
<h2 id="마침">마침</h2>
<p>구글링해서 약간의 힌트를 얻어서 풀었다 ㅠㅠ
다음엔 좀 더 혼자 고민해보고 풀어야 겠다</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[카데인 알고리즘 : 최대 부분합]]></title>
            <link>https://velog.io/@w-beom/%EC%B9%B4%EB%8D%B0%EC%9D%B8-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%B5%9C%EB%8C%80-%EB%B6%80%EB%B6%84%ED%95%A9</link>
            <guid>https://velog.io/@w-beom/%EC%B9%B4%EB%8D%B0%EC%9D%B8-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%B5%9C%EB%8C%80-%EB%B6%80%EB%B6%84%ED%95%A9</guid>
            <pubDate>Sat, 04 Apr 2020 15:12:39 GMT</pubDate>
            <description><![CDATA[<p>요즘 Leetcoding 30일 챌린지를 하면서 알고리즘 연습을 하고있던 중 
최대 부분합 문제를 풀게 되었는데 아직 알고리즘에 지식이 얕아서 <sub>(문제는 해결했지만)</sub> 
효율성에 있어서 매우 떨어지는 알고리즘만 작성하게 되더라 ㅠㅠ</p>
<p>그래서 오늘은 최대부분합 문제에서 사용하는 카데인 알고리즘에 대해 공부하고 작성하려고 한다.
 해당 문제를 보고 오면 더욱 이해가 쉬울거라 생각한다.
<a href="https://velog.io/@w-beom/30-Day-LeetCoding-Challenge-3Day">문제보기</a></p>
<hr>
<h2 id="카데인-알고리즘-kadanes-algorithm">카데인 알고리즘 (Kadane&#39;s Algorithm)</h2>
<h3 id="최대부분합">최대부분합</h3>
<blockquote>
<p>어떤 숫자의 배열이 주어졌을 때 (집합과 달리 각 원소의 자리나 순서를 바꿀 수 없습니다), 연속한 숫자들로 이루어진 부분 배열 중 합이 최대가 되는 경우를 구하는 문제입니다. 
예를 들어 [-2, 1, -3, 4, -1, 2, 1, -5, 4]의 배열이 주어졌다면 [4, -1, 2, 1]의 연속한 네 원소로 이루어진 연속 부분 배열의 경우에 합이 6으로 최대가 됩니다.</p>
</blockquote>
<p>처음 이 문제를 보았을 때 단순무식하게 이중 for문을 돌려 O(n<sup>2</sup>)이 걸리는 알고리즘을 작성하였는데 문제 마지막 줄에 O(n)의 효율로도 생각해보라는 문구가 있어서 구글링에 검색하게 됐다. 
내가 처음 접근했던 방식과 카데인 알고리즘의 접근 방식을 비교해보자</p>
<h4 id="내가-처음-접근했던-방식">내가 처음 접근했던 방식</h4>
<pre>
주어진 배열 [-2, 1, -3, 4, -1, 2, 1, -5, 4]

sum{-2}, sum{-2,1}, sum{-2,1,3} ... sum{-2, 1, -3, 4, -1, 2, 1, -5, 4}
sum{1}, sum{1,-3} ... sum{1, -3, 4, -1, 2, 1, -5, 4}
...
sum{4}
</pre>

<h4 id="카데인-알고리즘으로-접근한-방식">카데인 알고리즘으로 접근한 방식</h4>
<pre>
주어진 배열 [-2, 1, -3, 4, -1, 2, 1, -5, 4]

sum{-2}
sum{-2,1}, sum{1}
sum{-2,1,-3}, sum{1,-3}, sum{-3}
sum{-2,1,-3,4}, sum{1,-3,4}, sum{-3,4}, sum{4}
...
</pre>

<p>내가 처음 접근한 방식은 배열의 i번째 인덱스부터 시작하여 각 부분배열의 합을 더한 후 최댓값을 반환하는 방식이다. 근데 이렇게 접근해버리면 처음 부터 끝까지 부분배열의 합을 계산해야 하니 효율성에서 뒤떨어지는 방식이다.</p>
<p>카데인 알고리즘은 배열의 i번째 인덱스를 끝을 기준으로 잡는다. 그렇게 되면
i+1번째 원소에서 부분배열의 합들의 최댓값은 i번째 부분배열에 i+1번째 원소를 더한 값과 i+1번째 원소를 비교한 최댓값이 결과로 나오게 된다.
이게 무슨 소린가 하면</p>
<ul>
<li>i=0일 때 sum{-2} 최댓값은 -2</li>
<li>i=1일 때 sum{-2,1} = -1, sum{1} = 1 
즉 i=1일 때의 부분집합 sum{-2,1}은 i=0일 때의 부분집합에 i+1번째 원소를 더한것과 sum{1}을 비교하여 최댓값 1을 반환</li>
<li>i=2일 때 sum{-2,1,-3}, sum{1,-3}, sum{-3}중 최댓값을 찾아야 하는데 이미 i=1일때의 최댓값을 우리는 알고있다.
sum{-2,1} -3 <sub>(i+1의 원소)</sub> &lt; sum{1} -3 <sub>(i+1의 원소)</sub> 이 더 큰걸 알고있으니 sum{1,-3}과 sum{-3}만 비교하면 되는 것이다.</li>
</ul>
<p>이렇게 배열의 끝까지 n번만 순회하면 부분집합의 최댓값을 찾을 수 있어서 O(n)의 시간복잡도를 가지게 된다!
코드</p>
<hr>
<pre><code class="language-java">public static int maxSubArray2(int[] nums) {
    int maxSum = 0;
    int endMax = Integer.MIN_VALUE;
    for (int i = -1; i &lt; nums.length - 1; i++) {
        maxSum = Math.max(maxSum + nums[i + 1], nums[i + 1]);
        endMax = Math.max(maxSum, endMax);
    }
    return endMax;
}</code></pre>
<h2 id="마침">마침</h2>
<p>처음으로 블로그에 알고리즘을 글로 작성하려고 보니 너무 어렵다.... 
솔직히 글을 쓰면서도 이해가 잘 안돼서 계속 찾아봤다 ㅠㅠ
게다가 글 쓰는 재주도 없으니 다른 분들이 봤을 때 이해를 잘 할 수 있을까라는 걱정도 든다...
점점 쓰다 보면 나아지겠지 ㅠㅠ</p>
<h3 id="참고">참고</h3>
<p><a href="https://topdori.com/?p=234">카데인 알고리즘 : 최대 부분합 문제</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[30-Day LeetCoding Challenge - 4Day (Move Zeroes)]]></title>
            <link>https://velog.io/@w-beom/30-Day-LeetCoding-Challenge-4Day-Move-Zeroes</link>
            <guid>https://velog.io/@w-beom/30-Day-LeetCoding-Challenge-4Day-Move-Zeroes</guid>
            <pubDate>Sat, 04 Apr 2020 08:42:00 GMT</pubDate>
            <description><![CDATA[<h2 id="move-zeroes">Move Zeroes</h2>
<p>Given an array nums, write a function to move all 0&#39;s to the end of it while maintaining the relative order of the non-zero elements.</p>
<pre>
Example:

Input: [0,1,0,3,12]
Output: [1,3,12,0,0]
</pre>

<p>Note:
1.You must do this in-place without making a copy of the array.
2.Minimize the total number of operations.</p>
<p>배열이 주어지면 순서 그대로 0만 뒤로 내보내는 문제이다.</p>
<h2 id="solution">Solution</h2>
<pre><code class="language-java">class Solution {
    public void moveZeroes(int[] nums) {
        int temp = 0;
    for (int i = 0; i &lt; nums.length; i++) {
        for (int j = i+1; j &lt; nums.length; j++) {
            if (nums[i] == 0) {
                temp = nums[i];
                nums[i] = nums[j];
                nums[j] = temp;
            }
        }
    }
   }
}</code></pre>
<p>요즘 문제를 풀면서 느끼는 건데 효율성을 고려하면서 알고리즘을 짜는건 정말 어렵다...
ㅠㅠㅠ 항상 왜 나는 이중 for문 밖에 생각이 안날까</p>
]]></description>
        </item>
    </channel>
</rss>