<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>jcoding-play.log</title>
        <link>https://velog.io/</link>
        <description>한걸음씩 성장하는 개발자</description>
        <lastBuildDate>Sun, 10 Dec 2023 15:46:05 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>jcoding-play.log</title>
            <url>https://velog.velcdn.com/images/jcoding-play/profile/6475ec27-e934-4606-ba03-934447324840/social_profile.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. jcoding-play.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/jcoding-play" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[우아한테크코스 6기] 프리코스 4주차 회고 && 최종 리팩토링 후기]]></title>
            <link>https://velog.io/@jcoding-play/%EC%9A%B0%EC%95%84%ED%95%9C%ED%85%8C%ED%81%AC%EC%BD%94%EC%8A%A4-6%EA%B8%B0-%ED%94%84%EB%A6%AC%EC%BD%94%EC%8A%A4-4%EC%A3%BC%EC%B0%A8-%ED%9A%8C%EA%B3%A0-%EC%B5%9C%EC%A2%85-%EB%A6%AC%ED%8C%A9%ED%86%A0%EB%A7%81-%ED%9B%84%EA%B8%B0</link>
            <guid>https://velog.io/@jcoding-play/%EC%9A%B0%EC%95%84%ED%95%9C%ED%85%8C%ED%81%AC%EC%BD%94%EC%8A%A4-6%EA%B8%B0-%ED%94%84%EB%A6%AC%EC%BD%94%EC%8A%A4-4%EC%A3%BC%EC%B0%A8-%ED%9A%8C%EA%B3%A0-%EC%B5%9C%EC%A2%85-%EB%A6%AC%ED%8C%A9%ED%86%A0%EB%A7%81-%ED%9B%84%EA%B8%B0</guid>
            <pubDate>Sun, 10 Dec 2023 15:46:05 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>이 글은 프리코스 4주차 회고 글, 그리고 프리코스가 끝난 뒤 진행한 리팩토링 결과까지 포함하고 있다는 것을 미리 말씀드립니다!! 😀
리팩토링 후 달라진 점도 궁금하신 분들을 끝까지 다 봐주세요!~🧐🧐</p>
</blockquote>
<hr>
<h1 id="프리코스-3주-차-공통-피드백">프리코스 3주 차 공통 피드백</h1>
<p>4주 차 미션에 대한 글을 작성하기 이전에 먼저 <code>3주 차 공통 피드백</code> 내용들은 다음과 같습니다.</p>
<blockquote>
<ul>
<li>함수(메서드) 라인에 대한 기준</li>
</ul>
</blockquote>
<ul>
<li>발생할 수 있는 예외 상황에 대해 고민한다</li>
<li>비즈니스 로직과 UI 로직을 분리한다</li>
<li>연관성이 있는 상수는 static final 대신 enum을 활용한다</li>
<li>final 키워드를 사용해 값의 변경을 막는다</li>
<li>객체의 상태 접근을 제한한다</li>
<li>객체는 객체스럽게 사용한다</li>
<li>필드(인스턴스 변수)의 수를 줄이기 위해 노력한다</li>
<li>성공하는 케이스 뿐만 아니라 예외에 대한 케이스도 테스트한다</li>
<li>테스트 코드도 코드다</li>
<li>테스트를 위한 코드는 구현 코드에서 분리되어야 한다</li>
<li>단위 테스트하기 어려운 코드를 단위 테스트하기</li>
<li>private 함수를 테스트 하고 싶다면 클래스(객체) 분리를 고려한다</li>
</ul>
<p>3주 차 공통 피드백에 대해 자세히 보고 싶은 분은 <a href="https://docs.google.com/document/d/1Uhui2vf26U6t5XG4b1IvB26NtZR8as-QeU_ap_-Q6Y0/edit">3주 차 공통 피드백</a>에서 볼 수 있습니다.</p>
<p>4주 차 미션을 구현하기 이전 <code>3주 차 공통 피드백</code> 내용들을 준수할 수 있도록 노력했습니다.</p>
<h1 id="4주차">4주차</h1>
<hr>
<h2 id="크리스마스-프로모션">크리스마스 프로모션</h2>
<p>4주차 미션은 <code>크리스마스 프로모션</code>을 구현하는 것이었습니다.
미션을 진행하는 방식은 다음과 같이 나와있었습니다.</p>
<blockquote>
<p>🔍 진행 방식</p>
</blockquote>
<ul>
<li>미션은 기능 <strong>요구 사항, 프로그래밍 요구 사항, 과제 진행 요구 사항</strong> 세 가지로 구성되어 있다.</li>
<li>세 개의 요구 사항을 만족하기 위해 노력한다. 특히 기능을 구현하기 전에 기능 목록을 만들고, 기능 단위로 커밋 하는 방식으로 진행한다.</li>
<li>기능 요구 사항에 기재되지 않은 내용은 스스로 판단하여 구현한다.</li>
</ul>
<p>매주 마찬가지로 미션을 진행하기 전, 코수타 영상을 보게 되었습니다. 
코수타 영상에서 _<strong>4주 차 미션이 엄청 어렵다</strong>_고 언급해주셨고, 실제로 미션을 확인했을 때는 <strong>충격</strong>에 빠져버렸습니다...</p>
<p>3주 차 미션까지 그래도 시간 안에 구현을 완성하면서 구현 못할 정도로 어렵진 않겠지??라고 생각했었는데... 정말 정말 미션을 처음 확인하고 이게 맞나?? 라고 생각이 들었을 정도로 충격에 빠졌었습니다.</p>
<blockquote>
<p>주어진 요구 사항 중 일부를 가져와보았습니다!!</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/jcoding-play/post/eddd9bc0-3046-4863-b25d-ba9919d66577/image.png" alt=""></p>
<p>하지만 얼른 정신을 차리고 해낼 수 있다는 마음 가짐을 통해 미션을 구현해나가기 시작했습니다. (<strong><em><del>근데 정말 어려웠어요....</del></em></strong>)</p>
<hr>
<h4 id="요구-사항-분석">요구 사항 분석</h4>
<p>이전 주차 공통 피드백을 통해, 기능 목록을 작성할 때 <strong>변경 가능한 <code>클래스명</code>이나 <code>메서드명</code> 등을 작성하지 않는 것이 좋다는 것</strong>을 알게 되었습니다. </p>
<p>이번 주차에서도 이를 기억하고 <strong>최대한 변경 가능한 부분들에 대해선 자세히 작성하지 않도록 신경</strong>쓰면서 요구 사항을 작성해볼 수 있었습니다.</p>
<pre><code># 🎄 미션 - 크리스마스 프로모션 🎄

## 12월 이벤트를 위한 개발 요청

- 이벤트 예산은 걱정 안해도 된다.

### 메뉴
- 에피타이저: 양송이수프(6,000), 타파스(5,500), 시저샐러드(8,000)
- 메인: 티본스테이크(55,000), 바비큐립(54,000), 해산물파스타(35,000), 크리스마스파스타(25,000)
- 디저트: 초코케이크(15,000), 아이스크림(5,000)
- 음료: 제로콜라(3,000), 레드와인(60,000), 샴페인(25,000)

### 이벤트 목표
- 중복된 할인과 증정을 허용해서, 고객들이 혜택을 많이 받는다는 것을 체감
- 올해 12월에 지난 5년 중 최고의 판매 금액을 달성
- 12월 이벤트 참여 고객의 5%가 내년 1월 새해 이벤트에 재참여

### 12월 이벤트 계획
- 크리스마스 디데이 할인
  - 이벤트 기간: 2023.12.1 ~ 2023.12.25
  - 1,000원으로 시작하여 크리스마스가 다가올수록 날마다 할인 금액이 100원씩 증가
  - 총 주문 금액에서 해당 금액만큼 할인 (e.g.시작일인 12월 1일에 1,000원, 2일에 1,100원, ... 25일엔 3,400원 할인)
- 평일 할인(일요일 ~ 목요일): 평일에는 **디저트 메뉴를 메뉴 1개당 2,023원 할인**
- 주말 할인(금요일, 토요일): 주말에는 **메인 메뉴를 메뉴 1개당 2,023원 할인**
- 특별 할인: 이벤트 달력에 별이 있으면 **총주문 금액에서 1,000원 할인**
  - 달력에 별이 있는 날: 12/3, 12/10, 12/17, 12/24, 12/25, 12/31
- 증정 이벤트: 할인 전 총주문 금액이 12만 원 이상일 때, 샴페인 1개 증정
- 이벤트 기간: &#39;크리스마스 디데이 할인&#39;을 제외한 다른 이벤트는 2023.12.1 ~ 2023.12.31 동안 적용

### 혜택 금액에 따른 12월 이벤트 배지 부여
- 총혜택 금액에 따라 다른 이벤트 배지를 부여
  - 5천 원 이상: 별
  - 1만 원 이상: 트리
  - 2만 원 이상: 산타

### 고객에게 안내할 이벤트 주의 사항
- 총주문 금액 10,000원 이상부터 이벤트가 적용
- 음료만 주문 시, 주문할 수 없음
- 메뉴는 한 번에 최대 20개까지만 주문 가능 (e.g.시저샐러드-1, 티본스테이크-3의 총개수는 4)

### &#39;12월 이벤트 플래너&#39; 개발 요청 사항
- 고객들이 식당에 방문할 날짜와 메뉴를 미리 선택하면 이벤트 플래너가 주문 메뉴, 할인 전 총주문 금액, 증정 메뉴, 혜택 내역, 총혜택 금액, 할인 후 예상 결제 금액, 12월 이벤트 배지 내용을 보여주기를 기대합니다.

#### 입력
- 12월 중 식당 예상 방문 날짜는 언제인가요? (숫자만 입력해 주세요!)
  - 방문할 날짜는 1 이상 31 이하의 숫자로만 입력받아 주세요.
  - 1 이상 31 이하의 숫자가 아닌 경우, &quot;[ERROR] 유효하지 않은 날짜입니다. 다시 입력해 주세요.&quot;라는 에러 메시지를 보여 주세요.
  - 모든 에러 메시지는 &quot;[ERROR]&quot;로 시작하도록 작성해 주세요.
- 주문하실 메뉴와 개수를 알려 주세요. (e.g. 해산물파스타-2,레드와인-1,초코케이크-1)
  - 고객이 메뉴판에 없는 메뉴를 입력하는 경우, &quot;[ERROR] 유효하지 않은 주문입니다. 다시 입력해 주세요.&quot;라는 에러 메시지를 보여 주세요.
  - 메뉴의 개수는 1 이상의 숫자만 입력되도록 해주세요. 이외의 입력값은 &quot;[ERROR] 유효하지 않은 주문입니다. 다시 입력해 주세요.&quot;라는 에러 메시지를 보여 주세요.
  - 메뉴 형식이 예시와 다른 경우, &quot;[ERROR] 유효하지 않은 주문입니다. 다시 입력해 주세요.&quot;라는 에러 메시지를 보여 주세요.
  - 중복 메뉴를 입력한 경우(e.g. 시저샐러드-1,시저샐러드-1), &quot;[ERROR] 유효하지 않은 주문입니다. 다시 입력해 주세요.&quot;라는 에러 메시지를 보여 주세요.
  - 모든 에러 메시지는 &quot;[ERROR]&quot;로 시작하도록 작성해 주세요.

#### 출력
- 주문 메뉴의 출력 순서는 자유롭게 출력해 주세요.
- 총혜택 금액에 따라 이벤트 배지의 이름을 다르게 보여 주세요.
- 총혜택 금액 = 할인 금액의 합계 + 증정 메뉴의 가격
- 할인 후 예상 결제 금액 = 할인 전 총주문 금액 - 할인 금액
- 증정 메뉴
  - 증정 이벤트에 해당하지 않는 경우, 증정 메뉴 &quot;없음&quot;으로 보여 주세요.
- 혜택 내역
  - 고객에게 적용된 이벤트 내역만 보여 주세요.
  - 적용된 이벤트가 하나도 없다면 혜택 내역 &quot;없음&quot;으로 보여 주세요.
  - 혜택 내역에 여러 개의 이벤트가 적용된 경우, 출력 순서는 자유롭게 출력해주세요.
- 이벤트 배지
  - 이벤트 배지가 부여되지 않는 경우, &quot;없음&quot;으로 보여 주세요.

---

## 🕹 구현할 기능 목록

### 변환 기능
- [x] 입력 형식에 맞게 입력된 메뉴와 개수를 통해 주문 메뉴를 알 수 있다. 

### 날짜 기능
- [x] 12월 중 식당 예상 방문 날짜를 알 수 있다.
  - [x] **[예외 처리]** 예상 방문 날짜가 1보다 작거나 31보다 크면 예외가 발생한다.

### 메뉴 기능
- [x] 메뉴에 대한 타입을 알 수 있다.
  - [x] 타입은 애피타이저, 메인, 디저트, 음료로 총 4가지로 구성되어있다.
- [x] 타입별 메뉴를 알 수 있다.
  - [x] 에피타이저: 양송이수프(6,000), 타파스(5,500), 시저샐러드(8,000)
  - [x] 메인: 티본스테이크(55,000), 바비큐립(54,000), 해산물파스타(35,000), 크리스마스파스타(25,000)
  - [x] 디저트: 초코케이크(15,000), 아이스크림(5,000)
  - [x] 음료: 제로콜라(3,000), 레드와인(60,000), 샴페인(25,000)
- [x] 이름을 통해 메뉴를 찾을 수 있다.

### 주문 기능
- [x] 주문할 메뉴와 개수를 알 수 있다.
  - [x] **[예외 처리]** 주문한 메뉴의 개수가 1보다 작을 경우, &quot;[ERROR] 유효하지 않은 주문입니다. 다시 입력해 주세요.&quot;라는 메시지와 함께 예외가 발생한다.
  - [x] **[예외 처리]** 메뉴판에 없는 메뉴가 입력된 경우, &quot;[ERROR] 유효하지 않은 주문입니다. 다시 입력해 주세요.&quot;라는 메시지와 함께 예외가 발생한다.
- [x] 전체 주문 내역을 알 수 있다.
  - [x] 전체 주문 내역이 올바르지 검증할 수 있다. 
  - [x] **[예외 처리]** 주문한 메뉴가 없는 경우, 예외가 발생한다.
  - [x] **[예외 처리]** 중복 메뉴가 존재하는 경우, &quot;[ERROR] 유효하지 않은 주문입니다. 다시 입력해 주세요.&quot;라는 메시지와 함께 예외가 발생한다.
  - [x] **[예외 처리]** 음료만 주문 시, 예외가 발생한다.
  - [x] **[예외 처리]** 메뉴의 총 개수가 20개를 넘을 시, 예외가 발생한다.
- [x] 할인 전 총주문 금액을 계산할 수 있다.

- [x] 주문을 할 수 있다.

### 할인 기능
- [x] 진행하는 할인 종류를 알 수 있다.
  - [x] 크리스마스 디데이 할인
  - [x] 평일 할인(일요일 ~ 목요일)
  - [x] 주말 할인(금요일, 토요일)
  - [x] 특별 할인
  - [x] 증정 이벤트

- [x] 할인 종류 별 적용 가능한 날짜를 알 수 있다.
  - [x] 크리스마스 디데이 할인
    - [x] 이벤트 기간: 2023.12.1 ~ 2023.12.25
  - [x] 평일 할인(일요일 ~ 목요일)
    - [x] 12월의 모든 평일 (일요일 ~ 목요일)
  - [x] 주말 할인(금요일, 토요일)
    - [x] 12월의 모든 주말 (금요일, 토요일)
  - [x] 특별 할인
    - [x] 달력에 별이 있는 날: 12/3, 12/10, 12/17, 12/24, 12/25, 12/31
  - [x] 증정 이벤트
    - [x] 12월의 모든 날짜

### 혜택 기능
- [x] 고객에게 적용 가능한 이벤트 내역을 알 수 있다.
  - [x] 총주문 금액 10,000원 이상부터 이벤트가 적용

- [x] 고객에게 적용 가능한 총혜택 내역을 확인할 수 있다.
  - [x] 크리스마스 디데이 할인
    - [x] 1,000원으로 시작하여 크리스마스가 다가올수록 날마다 할인 금액이 100원씩 증가
    - [x] 총 주문 금액에서 해당 금액만큼 할인 (e.g.시작일인 12월 1일에 1,000원, 2일에 1,100원, ... 25일엔 3,400원 할인)
    - [x] 평일 할인(일요일 ~ 목요일)
      - [x] 평일에는 디저트 메뉴를 메뉴 1개당 2,023원 할인
    - [x] 주말 할인(금요일, 토요일)
      - [x] 주말에는 메인 메뉴를 메뉴 1개당 2,023원 할인
    - [x] 특별 할인
      - [x] 이벤트 달력에 별이 있으면 총주문 금액에서 1,000원 할인
    - [x] 증정 이벤트
      - [x] 할인 전 총주문 금액이 12만 원 이상일 때, 샴페인 1개 증정 

- [x] 총혜택 금액을 알 수 있다. (할인 금액의 합계 + 증정 메뉴의 가격)
- [x] 할인 후 예상 결제 금액을 알 수 있다.
  - [x] 할인 전 총주문 금액 - 할인 금액

- [x] 총혜택 금액에 따라 고객에게 적용 가능한 이벤트 배지를 알 수 있다.
  - [x] 5천 원 이상: 별
  - [x] 1만 원 이상: 트리
  - [x] 2만 원 이상: 산타

- [x] 증정 메뉴를 받을 수 있다.

### 입력 기능
- [x] **[공통 예외 처리]** 입력이 공백이면 예외가 발생한다.
- [x] 12월 중 식당 예상 방문 날짜를 입력받는다.
  - [x] **[예외 처리]** 예상 방문 날짜에 대한 입력이 숫자가 아니라면 예외가 발생한다.
- [x] 주문하실 메뉴와 개수를 입력받는다. (e.g. 해산물파스타-2,레드와인-1,초코케이크-1)
  - [x] **[예외 처리]** 메뉴 입력 형식이 예시와 다른 경우, &quot;[ERROR] 유효하지 않은 주문입니다. 다시 입력해 주세요.&quot;라는 메시지와 함께 예외가 발생한다.
- [x] 사용자가 잘못된 값을 입력한 경우 예외를 발생시키고, 그 부분부터 다시 입력을 받는다.

### 출력 기능
- [x] 시작 메세지를 출력한다.
- [x] 이벤트 혜택을 미리 보여준다는 메시지를 출력한다.
- [x] 총 주문 메뉴를 출력한다.
  - [x] 총 주문 메뉴들을 순서 상관없이 자유롭게 출력한다.
- [x] 식당에서 받을 이벤트 혜택을 출력한다.
  - [x] 할인 전 총 주문 금액을 출력한다.
  - [x] 증정 메뉴를 출력한다.
    - [x] 증정 이벤트에 해당하지 않는 경우, 증정 메뉴 &quot;없음&quot;으로 출력한다.
  - [x] 혜택 내역을 출력한다.
    - [x] 고객에게 적용된 이벤트 내역만 출력한다.
    - [x] 적용된 이벤트가 하나도 없다면 혜택 내역 &quot;없음&quot;으로 출력한다.
    - [x] 혜택 내역에 여러 개의 이벤트가 적용된 경우, 출력 순서는 자유롭게 출력한다.
  - [x] 총 혜택 금액을 출력한다.
  - [x] 할인 후 예상 결제 금액을 출력한다.
  - [x] 총혜택 금액에 따라 이벤트 배지의 이름을 다르게 출력한다.
    - [x] 이벤트 배지가 부여되지 않는 경우, &quot;없음&quot;으로 출력한다.
- [x] 예외가 발생한 경우, 예외 메세지를 출력한다.
  - [x] 모든 예외 메세지는 &quot;[ERROR]&quot;로 시작한다.

---

## 🚨 과제 제출 전 체크 리스트
- [x] 요구 사항에 명시된 출력값 형식을 지켰는지 확인
- [x] 모든 테스트가 성공하는지 확인
  - [x] 터미널에서 `./gradlew clean test`가 통과하는지 확인
  - [x] `ApplicationTest`의 모든 테스트가 통과하는지 확인 
- [x] 자바 버전이 17인지 확인
- [x] indent depth가 3을 넘지 않는지 확인 (2까지만 허용)
- [x] 3항 연산자를 썼는지 확인 (3항 연산자를 허용 x)
- [x] 함수의 길이가 15라인을 넘어가는지 확인 (15라인 넘어가는 것을 허용 x)
- [x] else 예약어를 썼는지 확인 (else 예약어를 허용 x)
- [x] 도메인 로직에 단위 테스트를 구현했는지 확인
- [x] 예외가 발생한 경우, IllegalArgumentException을 발생시키고 &quot;[ERROR]&quot;로 시작하는 에러 메시지를 출력 후 그 부분부터 입력받는지 확인
- [x] 사용자가 입력하는 값은 `camp.nextstep.edu.missionutils.Console`의 `readLine()`을 활용하는지 확인</code></pre><p>이건 제가 4주 차 미션을 진행하기 이전 <code>크리스마스 프로모션 규칙</code>과 <code>구현할 기능 목록</code>을 작성한 내용들입니다.</p>
<hr>
<h2 id="핵심-기능">핵심 기능</h2>
<h3 id="절차지향적-검증">절차지향적 검증</h3>
<p>이번 미션에서 주어진 <code>예외 상황</code>들을 빠뜨리지 않기 위해 꼼꼼히 살펴보고 작성해보았습니다.</p>
<p>그 중, <code>전체 주문 내역</code>에 대해서 다음과 같이 예외 상황들을 작성할 수 있었습니다.</p>
<blockquote>
<ul>
<li>전체 주문 내역 검증</li>
</ul>
</blockquote>
<ul>
<li><input disabled="" type="checkbox"> <strong>[예외 처리]</strong> 주문한 메뉴가 없는 경우, 예외가 발생한다.</li>
<li><input disabled="" type="checkbox"> <strong>[예외 처리]</strong> 중복 메뉴가 존재하는 경우, &quot;[ERROR] 유효하지 않은 주문입니다. 다시 입력해 주세요.&quot;라는 메시지와 함께 예외가 발생한다.</li>
<li><input disabled="" type="checkbox"> <strong>[예외 처리]</strong> 음료만 주문 시, 예외가 발생한다.</li>
<li><input disabled="" type="checkbox"> <strong>[예외 처리]</strong> 메뉴의 총 개수가 20개를 넘을 시, 예외가 발생한다.</li>
</ul>
<p>이를 <code>Orders</code>라는 <strong>전체 주문 내역</strong> 객체 안에서 검증하는 기능을 함께 구현하면 <code>Orders</code> 객체는 <strong>실제 주문 관련 메서드</strong>보다 <strong>검증 관련 메서드</strong>들이 대부분 위치하게 되었습니다.</p>
<p>이를 해결하기 위해, <code>OrderValidator</code>라는 <strong>주문 내역을 검증하는 객체</strong>를 새로 만들어보았습니다.</p>
<pre><code class="language-java">public class OrderValidator {
    protected static final String INVALID_ORDER_EXCEPTION_MESSAGE = &quot;유효하지 않은 주문입니다. 다시 입력해 주세요.&quot;;
    private static final String ORDER_ONLY_DRINKS_EXCEPTION_MESSAGE = &quot;음료만 주문할 수 없습니다. 다시 입력해 주세요.&quot;;
    private static final String LARGER_THAN_MAXIMUM_ORDER_COUNT_EXCEPTION_FORMAT =
            &quot;메뉴는 최대 20개까지만 주문할 수 있습니다. 다시 입력해 주세요. (현재 주문한 메뉴 개수 = %d)&quot;;

    private static final int MAXIMUM_ORDER_COUNT = 20;

    public void validate(List&lt;Order&gt; orders) {
        validateEmptyOrder(orders);
        validateDuplicatedMenu(orders);
        validateOrderOnlyDrinks(orders);
        validateTotalOrderCount(orders);
    }

    private void validateEmptyOrder(List&lt;Order&gt; orders) {
        if (orders.isEmpty()) {
            throw new IllegalArgumentException(INVALID_ORDER_EXCEPTION_MESSAGE);
        }
    }

    private void validateDuplicatedMenu(List&lt;Order&gt; orders) {
        if (hasDuplicatedMenu(orders)) {
            throw new IllegalArgumentException(INVALID_ORDER_EXCEPTION_MESSAGE);
        }
    }

    private boolean hasDuplicatedMenu(List&lt;Order&gt; orders) {
        return orders.stream()
                .map(Order::getMenu)
                .distinct()
                .count() != orders.size();
    }

    private void validateOrderOnlyDrinks(List&lt;Order&gt; orders) {
        if (hasOrderedOnlyDrinks(orders)) {
            throw new IllegalArgumentException(ORDER_ONLY_DRINKS_EXCEPTION_MESSAGE);
        }
    }

    private boolean hasOrderedOnlyDrinks(List&lt;Order&gt; orders) {
        return orders.stream()
                .allMatch(Order::isDrink);
    }

    private void validateTotalOrderCount(List&lt;Order&gt; orders) {
        int totalOrderCount = calculateTotalOrderCount(orders);

        if (isLargerThanMaximumOrderCount(totalOrderCount)) {
            throw new IllegalArgumentException(
                    String.format(LARGER_THAN_MAXIMUM_ORDER_COUNT_EXCEPTION_FORMAT, totalOrderCount));
        }
    }

    private int calculateTotalOrderCount(List&lt;Order&gt; orders) {
        return orders.stream()
                .map(Order::getCount)
                .reduce(Constants.INITIAL_COUNT, Integer::sum);
    }

    private boolean isLargerThanMaximumOrderCount(int totalOrderCount) {
        return totalOrderCount &gt; MAXIMUM_ORDER_COUNT;
    }
}</code></pre>
<p>이처럼 <code>OrderValidator</code>라는 주문 내역을 검증하는 클래스를 따로 만들어 <code>Orders</code> 객체의 대한 책임을 줄일 수 있었습니다.</p>
<p>그러면 다음과 같이 궁금하신 분들도 있을 거 같았습니다.</p>
<blockquote>
<p>어?? 그러면 모든 객체에 대해 검증하는 클래스를 다 따로 만들면 모든 객체가 해당 역할만 수행할 수 있어 무조건 따로 만드는게 좋은 거 아닌가요❓❓❓</p>
</blockquote>
<p>다음 질문에 대한 답을 간단하게 해보겠습니다. </p>
<p>제가 검증하는 클래스를 분리하면서 느낀 <code>장점</code>은 위에서 설명드린대로 <strong>객체들이 관련된 기능만을 가질 수 있어 가독성이 향상</strong>되고 <strong>클래스의 역할과 책임이 명확히 드러나며 유지보수성을 높일 수 있다</strong>는 것이었습니다.</p>
<p>반대로, <code>단점</code>으로 전체 주문 내역을 검증하기 위해서 <code>OrderValidator</code>의 <code>validate</code> 메서드를 <strong>필수적으로 호출해야만 전체 주문 내역이 올바른지 검증할 수 있다</strong>는 점이었습니다. 
만약 개발을 나 혼자서 진행하는 것이라면 까먹지 않고 <code>validate</code> 메서드를 호출할 수 있겠지만, 만약 팀 프로젝트로 진행하는 중이고 다른 구성원이 <code>validate</code> 메서드를 호출하지 않았다면 <strong>올바르지 않은 주문 내역을 통해 프로그램을 동작할 수 있다는 치명적인 단점</strong>을 포함할 수 있다는 것을 느꼈습니다.</p>
<p><strong>결론적으로 장점과 단점 모두 존재하고, 무분별하게 검증 클래스를 따로 만드는 것은 올바르지 않다고 생각할 수 있었습니다.</strong></p>
<hr>
<h3 id="enum--함수형-인터페이스-활용">Enum &amp;&amp; 함수형 인터페이스 활용</h3>
<p>3주 차 공통 피드백에 나온대로, <code>연관성이 있는 상수는 static final 대신 enum</code>을 활용하기 위해 노력해보았습니다.</p>
<p>요구 사항 중 <strong>총혜택 금액</strong>에 따라 다른 <code>이벤트 배지</code>를 부여해야 하는 요구 사항이 있었고, 이를 <code>Enum</code>을 이용하여 구현해볼 수 있었습니다.</p>
<pre><code class="language-java">public enum EventBadge {
    SANTA(&quot;산타&quot;, totalBenefitAmount -&gt; totalBenefitAmount &gt;= 20000),
    TREE(&quot;트리&quot;, totalBenefitAmount -&gt; totalBenefitAmount &gt;= 10000 &amp;&amp; totalBenefitAmount &lt; 20000),
    STAR(&quot;별&quot;, totalBenefitAmount -&gt; totalBenefitAmount &gt;= 5000 &amp;&amp; totalBenefitAmount &lt; 10000),
    NOTHING(&quot;없음&quot;, totalBenefitAmount -&gt; totalBenefitAmount &lt; 5000);

    private final String name;
    private final Predicate&lt;Integer&gt; predicate;

    EventBadge(String name, Predicate&lt;Integer&gt; predicate) {
        this.name = name;
        this.predicate = predicate;
    }

    public static EventBadge of(int totalBenefitAmount) {
        return Arrays.stream(values())
                .filter(eventBadge -&gt; eventBadge.isMatch(totalBenefitAmount))
                .findFirst()
                .orElse(NOTHING);
    }

    private boolean isMatch(int totalBenefitAmount) {
        return predicate.test(totalBenefitAmount);
    }

    public String getName() {
        return name;
    }
}</code></pre>
<p>또한, 총혜택 금액에 따라 이벤트 배지를 부여하기 위해 Predicate라는 함수형 인터페이스를 적용해보았습니다.</p>
<pre><code class="language-java">@FunctionalInterface
public interface Predicate&lt;T&gt; {

    boolean test(T t);
}    </code></pre>
<p>이는 <strong>1개의 인자를 통해 주어진 검증을 진행하고 결과를 참, 거짓으로 반환해주는 함수형 인터페이스</strong>입니다. </p>
<p>이를 통해, <strong>총혜택 금액</strong>에 따른 <code>이벤트 배지</code>를 쉽게 찾고 반환할 수 있었습니다.</p>
<hr>
<h3 id="추상-클래스-활용">추상 클래스 활용</h3>
<p>이번 미션에서 <strong>각각의 이벤트에 대한 클래스를 분리</strong>하였습니다.
이러한 이벤트 클래스들은 <code>공통적인 메서드와 변수</code>를 가지게 되었고, _<strong>이를 효과적으로 처리하기 위해 추상 클래스를 활용</strong>_해보았습니다.</p>
<pre><code class="language-java">public abstract class AbstractEvent implements Event {
    private final List&lt;Integer&gt; applicableDates;

    public AbstractEvent() {
        this.applicableDates = initializeApplicableDates();
    }

    abstract List&lt;Integer&gt; initializeApplicableDates();

    @Override
    public boolean isApplicable(VisitDate visitDate, Orders orders) {
        return visitDate.isIncludedIn(applicableDates);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        AbstractEvent that = (AbstractEvent) o;
        return Objects.equals(applicableDates, that.applicableDates);
    }

    @Override
    public int hashCode() {
        return Objects.hash(applicableDates);
    }
}</code></pre>
<p>이러한 추상 클래스의 장점으로 느낀 것은, <strong>구현 클래스에서 중복된 코드를 제거할 수 있어 코드의 가독성을 향상</strong>시킨다는 점이었습니다.</p>
<hr>
<h3 id="테스트-코드">테스트 코드</h3>
<p>모든 도메인 로직에 대한 테스트 코드를 작성하기 위해 노력했습니다.</p>
<p>여러 입력값을 이용하여 테스트를 진행하고 이를 통과함으로써, <code>프로그램의 안정성</code>을 <code>향상</code>시킬 수 있다는 느낌을 받을 수 있었습니다.
<img src="https://velog.velcdn.com/images/jcoding-play/post/56791af6-8870-4e57-b524-81aaaf1be5e5/image.png" alt=""></p>
<blockquote>
<p>테스트 코드를 작성하고 보니 총 120개의 테스트를 작성했고, 
모두 통과되었습니다. 😀😀</p>
</blockquote>
<hr>
<h2 id="마치며">마치며</h2>
<p>이게 마지막 미션이라는 생각이 안 믿길 정도로, 프리코스를 진행하며 많은 몰입과 노력을 통해 시간이 정말 빨리 갔다고 느꼈습니다.</p>
<p>다시 한번, 우아한테크코스를 진행한 나의 선택 스스로를 칭찬할 수 있었습니다. 👍</p>
<p>이번 미션에 대한 느낀점은 <strong>처음 미션을 보았을 때 정말 충격에 빠졌었지만, 막상 미션을 진행하는 과정이 너무 즐거웠습니다. 뿌듯!!</strong></p>
<p><em><strong>제발.... 합격할 수 있길...</strong></em> 🤣🤣🤣🤣</p>
<blockquote>
<p>저의 자세한 코드가 궁금하신 분은 아래 링크에서 확인할 수 있습니다.
<a href="https://github.com/jcoding-play/java-christmas-6-jcoding-play/tree/main">https://github.com/jcoding-play/java-christmas-6-jcoding-play/tree/main</a></p>
</blockquote>
<hr>
<h1 id="최종-리팩토링-후-달라진-부분">최종 리팩토링 후 달라진 부분</h1>
<p>이 부분은 프리코스가 끝나고 난 뒤 리팩토링을 진행하고 이전과 크게 달라진 점을 알려드리기 위해 작성해보았습니다!!</p>
<h2 id="menu-enum-객체-수정">Menu Enum 객체 수정</h2>
<p>이번 미션에서 메뉴에 대한 종류는 <code>애피타이저</code>, <code>메인</code>, <code>디저트</code>, <code>음료</code> 이렇게 4가지로 구성되어있었습니다.</p>
<p>그래서 이를 리팩토링 이전에는, 각 타입에 대해 하나씩 클래스를 구현하였습니다.</p>
<pre><code class="language-java">public enum Appetizer implements Menu {
    MUSHROOM_SOUP(&quot;양송이수프&quot;, 6000),
    TAPAS(&quot;타파스&quot;, 5500),
    CAESAR_SALAD(&quot;시저샐러드&quot;, 8000);

    private final String name;
    private final int price;

    Appetizer(String name, int price) {
        this.name = name;
        this.price = price;
    }

    @Override
    public boolean isMatchName(String name) {
        return this.name.equals(name);
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public int getPrice() {
        return price;
    }
}</code></pre>
<p>이에 대한 문제점으로 모든 타입에 대해서 <code>isMatch()</code>, <code>getName()</code>, <code>getPrice()</code> 메서드를 중복으로 작성하는 문제가 발생하였었습니다.</p>
<p>이를 다음과 같이 리팩토링하여, 중복되는 코드를 제거할 수 있었습니다.</p>
<pre><code class="language-java">public enum Menu {
    MUSHROOM_SOUP(&quot;양송이수프&quot;, 6_000, Type.APPETIZER),
    TAPAS(&quot;타파스&quot;, 5_500, Type.APPETIZER),
    CAESAR_SALAD(&quot;시저샐러드&quot;, 8_000, Type.APPETIZER),

    T_BONE_STEAK(&quot;티본스테이크&quot;, 55_000, Type.MAIN),
    BARBECUE_LIP(&quot;바비큐립&quot;, 54_000, Type.MAIN),
    SEAFOOD_PASTA(&quot;해산물파스타&quot;, 35_000, Type.MAIN),
    CHRISTMAS_PASTA(&quot;크리스마스파스타&quot;, 25_000, Type.MAIN),

    CHOCOLATE_CAKE(&quot;초코케이크&quot;, 15_000, Type.DESSERT),
    ICE_CREAM(&quot;아이스크림&quot;, 5_000, Type.DESSERT),

    ZERO_COLA(&quot;제로콜라&quot;, 3_000, Type.DRINK),
    RED_WINE(&quot;레드와인&quot;, 60_000, Type.DRINK),
    CHAMPAGNE(&quot;샴페인&quot;, 25_000, Type.DRINK),

    NOTHING(&quot;없음&quot;, 0, null);

    private final String name;
    private final int price;
    private final Type type;

    Menu(String name, int price, Type type) {
        this.name = name;
        this.price = price;
        this.type = type;
    }

    public static Menu findByName(String name) {
        return Arrays.stream(values())
                .filter(menu -&gt; menu.isMatchName(name))
                .findFirst()
                .orElseThrow(() -&gt; new IllegalArgumentException(&quot;일치하는 메뉴가 존재하지 않습니다.&quot;));
    }

    private boolean isMatchName(String name) {
        return this.name.equals(name);
    }

    public boolean isDrink() {
        return type.isDrink();
    }

    public boolean isMatchType(Type type) {
        return this.type == type;
    }

    public int getPrice() {
        return price;
    }
}</code></pre>
<hr>
<h2 id="dayofweek-enum-객체-활용">DayOfWeek Enum 객체 활용</h2>
<p>이번 미션에서 이벤트들은 각각 적용 가능한 날짜들이 명시되어있었습니다.</p>
<p>한 가지 예로, <strong>평일 할인</strong>을 통해 설명하겠습니다.
<strong>평일 할인</strong>은 요일이 <code>일요일</code>~<code>목요일</code> 사이라면 적용이 되는 할인이었습니다.</p>
<p>이를 구현하기 위해 리팩토링 이전에는, 일일이 일요일~ 목요일에 대한 날짜를 <code>for문</code>을 이용하여 구하였습니다.</p>
<pre><code class="language-java">private static final int FIRST_SUNDAY_DATE = 3;
private static final int FIRST_THURSDAY_DATE = 7;
private static final int LAST_DAY_OF_THE_EVENT = 31;
private static final int WEEK_DATE = 7;

List&lt;Integer&gt; initializeApplicableDates() {
    List&lt;Integer&gt; applicableDates = new ArrayList&lt;&gt;();

    for (int firstDate = FIRST_SUNDAY_DATE; firstDate &lt;= FIRST_THURSDAY_DATE; firstDate++) {
        for (int date = firstDate; date &lt;= LAST_DAY_OF_THE_EVENT; date = date + WEEK_DATE) {
            applicableDates.add(date);
        }
    }
    Collections.sort(applicableDates);
    return applicableDates;
}</code></pre>
<p>하지만, DayOfWeek라는 Java에서 제공하는 Enum 객체를 알게 되었고, 이를 통해 쉽게 적용 가능한 날짜를 구할 수 있었습니다.</p>
<pre><code class="language-java">private static Set&lt;DayOfWeek&gt; discountDaysOfWeek = Set.of(DayOfWeek.SUNDAY, DayOfWeek.MONDAY, DayOfWeek.TUESDAY, DayOfWeek.WEDNESDAY, DayOfWeek.THURSDAY);</code></pre>
<p>이를 이용하여 쉽게 할인이 적용 가능한지를 파악할 수 있었습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[우아한테크코스 6기] 프리코스 3주차 회고 && 최종 리팩토링 후기]]></title>
            <link>https://velog.io/@jcoding-play/%EC%9A%B0%EC%95%84%ED%95%9C%ED%85%8C%ED%81%AC%EC%BD%94%EC%8A%A4-6%EA%B8%B0-%ED%94%84%EB%A6%AC%EC%BD%94%EC%8A%A4-3%EC%A3%BC%EC%B0%A8-%ED%9A%8C%EA%B3%A0-%EC%B5%9C%EC%A2%85-%EB%A6%AC%ED%8C%A9%ED%86%A0%EB%A7%81</link>
            <guid>https://velog.io/@jcoding-play/%EC%9A%B0%EC%95%84%ED%95%9C%ED%85%8C%ED%81%AC%EC%BD%94%EC%8A%A4-6%EA%B8%B0-%ED%94%84%EB%A6%AC%EC%BD%94%EC%8A%A4-3%EC%A3%BC%EC%B0%A8-%ED%9A%8C%EA%B3%A0-%EC%B5%9C%EC%A2%85-%EB%A6%AC%ED%8C%A9%ED%86%A0%EB%A7%81</guid>
            <pubDate>Sat, 09 Dec 2023 08:56:03 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>이 글은 프리코스 3주차 회고 글, 그리고 프리코스가 끝난 뒤 진행한 리팩토링 결과까지 포함하고 있다는 것을 미리 말씀드립니다!! 😀
리팩토링 후 달라진 점도 궁금하신 분들을 끝까지 다 봐주세요!~🧐🧐</p>
</blockquote>
<hr>
<h1 id="프리코스-2주-차-공통-피드백">프리코스 2주 차 공통 피드백</h1>
<p>3주 차 미션에 대한 글을 작성하기 이전에 먼저 <code>2주 차 공통 피드백</code> 내용들은 다음과 같습니다.</p>
<blockquote>
<ul>
<li>README.md를 상세히 작성한다</li>
</ul>
</blockquote>
<ul>
<li>기능 목록을 재검토한다</li>
<li>기능 목록을 업데이트한다</li>
<li>값을 하드 코딩하지 않는다</li>
<li>구현 순서도 코딩 컨벤션이다</li>
<li>변수 이름에 자료형은 사용하지 않는다</li>
<li>한 함수가 한 가지 기능만 담당하게 한다</li>
<li>함수가 한 가지 기능을 하는지 확인하는 기준을 세운다</li>
<li>테스트를 작성하는 이유에 대해 본인의 경험을 토대로 정리해본다</li>
<li>처음부터 큰 단위의 테스트를 만들지 않는다</li>
</ul>
<p>2주 차 공통 피드백에 대해 자세히 보고 싶은 분은 <a href="https://docs.google.com/document/d/1TLO5hdoJgx78a8RDmaULrWfXxujsNPCKaj6_tnSYgg8/edit">2주 차 공통 피드백</a>에서 볼 수 있습니다.</p>
<p>3주 차 미션을 구현하기 이전 <code>2주 차 공통 피드백</code> 내용들을 준수할 수 있도록 노력했습니다.</p>
<h1 id="3주차">3주차</h1>
<hr>
<h2 id="로또">로또</h2>
<p>3주차 미션은 <code>로또</code>를 구현하는 것이었습니다.
미션을 진행하는 방식은 다음과 같이 나와있었습니다.</p>
<blockquote>
<p>🔍 진행 방식</p>
</blockquote>
<ul>
<li>미션은 기능 <strong>요구 사항, 프로그래밍 요구 사항, 과제 진행 요구 사항</strong> 세 가지로 구성되어 있다.</li>
<li>세 개의 요구 사항을 만족하기 위해 노력한다. 특히 기능을 구현하기 전에 기능 목록을 만들고, 기능 단위로 커밋 하는 방식으로 진행한다.</li>
<li>기능 요구 사항에 기재되지 않은 내용은 스스로 판단하여 구현한다.</li>
</ul>
<p>또한, 이전 미션과 다르게 이번 미션에선 제공된 <code>Lotto</code> 클래스를 활용해 구현하도록 되어있었습니다.</p>
<pre><code class="language-java">// 제공된 Lotto 클래스
public class Lotto {
    private final List&lt;Integer&gt; numbers;

    public Lotto(List&lt;Integer&gt; numbers) {
        validate(numbers);
        this.numbers = numbers;
    }

    private void validate(List&lt;Integer&gt; numbers) {
        if (numbers.size != 6) {
            throw new IllegalArgumentException();
        }
    }

    // TODO: 추가 기능 구현
}</code></pre>
<h4 id="요구-사항-분석">요구 사항 분석</h4>
<hr>
<p>이전 주차와 마찬가지로, 요구 사항을 정확히 준수하기 위해 노력했습니다.</p>
<p>미션을 진행하기 전에 요구 사항을 꼼꼼히 분석하고 이를 토대로 <code>구현할 기능 목록</code>을 작성하였습니다.</p>
<pre><code># 💸 미션 - 로또

## 📝 로또 게임 규칙

### 로또
- 로또 번호의 숫자 범위는 1~45까지이다.
- 1개의 로또를 발행할 때 중복되지 않는 6개의 숫자를 뽑는다.
- 당첨 번호 추첨 시 중복되지 않는 숫자 6개와 보너스 번호 1개를 뽑는다.
- 당첨은 1등부터 5등까지 있다.
- 당첨 기준
  - 1등: 6개 번호 일치
  - 2등: 5개 번호 일치 + 보너스 번호 일치
  - 3등: 5개 번호 일치
  - 4등: 4개 번호 일치
  - 5등: 3개 번호 일치
- 당첨 순위별 금액
  - 1등: 2,000,000,000원
  - 2등: 30,000,000원
  - 3등: 1,500,000원
  - 4등: 50,000원
  - 5등: 5,000원

### 입력
- 로또 구입 금액에 맞게 로또를 발행할 수 있다.
  - 로또 1장의 가격은 1,000원이다.
- 당첨 번호를 쉼표(,)를 기준으로 입력받는다
- 보너스 번호를 입력받는다.
- 사용자가 잘못된 값을 입력하면 `IllegalArgumentException`을 발생시키고 다시 입력받는다.

### 출력
- 발행한 로또 수량 및 번호를 출력한다.
  - 발행한 로또 번호는 오름차순으로 정렬하여 보여준다.
- 사용자의 로또 번호와 당첨 번호를 비교하여 당첨 내역 및 수익률을 출력한다.
  - 수익률은 소수점 둘째 자리에서 반올림한다.
- 예외가 발생할 경우 `[ERROR]`로 시작하는 에러 메시지를 출력한다.

---

## 🕹구현할 기능 목록

### 로또 기능 
- [x] 로또 번호는 1에서 45사이의 중복되지 않는 6개의 숫자를 가진다.
  - [x] **[예외 처리]** 로또 번호의 개수가 6개가 아니면 예외가 발생한다.
  - [x] **[예외 처리]** 로또 번호의 숫자들 중 1보다 작거나 45보다 큰 숫자가 있다면 예외가 발생한다.
  - [x] **[예외 처리]** 6개의 숫자 중 중복되는 숫자가 있다면 예외가 발생한다.

### 로또 구입 기능
- [x] 로또 구입 금액에 해당하는 만큼 로또를 발행할 수 있다.
  - [x] 로또 1장의 가격은 1,000원이다.
  - [x] **[예외 처리]** 로또 구입 금액이 1,000원보다 작다면 예외가 발생한다.
  - [x] **[예외 처리]** 로또 구입 금액이 1,000원으로 나누어 떨어지지 않으면 예외가 발생한다.

### 로또 생성 기능
- [x] 입력된 크기만큼 로또를 만들 수 있다.

### 보너스 번호 기능
- [x] 보너스 번호는 1부터 45사이의 숫자이다.
  - [x] **[예외 처리]** 보너스 번호가 1보다 작거나 45보다 크다면 예외가 발생한다.

### 당첨 로또 기능
- [x] 당첨 로또는 중복되지 않는 6개의 숫자와 보너스 번호 1개를 가진다.
  - [x] **[예외 처리]** 6개의 숫자 중 보너스 번호와 일치하는 숫자가 있으면 예외가 발생한다.
- [x] 1장의 로또와 당첨 번호를 비교하여 결과를 알 수 있다.
  - [x] 1등: 6개 번호 일치
  - [x] 2등: 5개 번호 일치 + 보너스 번호 일치
  - [x] 3등: 5개 번호 일치
  - [x] 4등: 4개 번호 일치
  - [x] 5등: 3개 번호 일치

### 당첨 상금 기능
- [x] 당첨 순위별 상금이 얼마인지 알 수 있다.
  - [x] 1등: 2,000,000,000원
  - [x] 2등: 30,000,000원
  - [x] 3등: 1,500,000원
  - [x] 4등: 50,000원
  - [x] 5등: 5,000원

### 당첨 통계 기능
- [x] 사용자가 구매한 총 로또와 당첨 번호를 비교하여 전체 당첨 내역을 알 수 있다.
- [x] 사용자가 구매한 로또 번호의 결과를 통해 수익률을 계산할 수 있다.

### 변환 기능
- [x] 문자열에서 숫자 리스트로 변환할 수 있다.
  - [x] 쉼표(,)를 기준으로 숫자를 분리한다.

### 입력 기능
- [x] **[공통 예외 처리]** 입력이 공백이면 예외가 발생한다.
- [x] 로또 구입 금액을 입력 받는다.
  - [x] **[예외 처리]** 로또 구입 금액에 대한 입력이 숫자가 아니면 예외가 발생한다.
- [x] 당첨 번호를 입력 받는다.
  - [x] **[예외 처리]** 당첨 번호에 대한 입력 형식이 올바르지 않으면 예외가 발생한다.
- [x] 보너스 번호를 입력 받는다.
  - [x] **[예외 처리]** 보너스 번호에 대한 입력이 숫자가 아니면 예외가 발생한다.
- [x] 사용자가 잘못된 값을 입력할 경우 IllegalArgumentException를 발생시키고, 그 부분부터 입력을 다시 받는다.

### 출력 기능
- [x] 발행한 로또 수량 및 번호를 출력한다.
  - [x] 로또 번호는 오름차순으로 정렬하여 출력한다.
- [x] 당첨 내역을 출력한다.
- [x] 로또 결과에 대한 총 수익률을 출력한다.
  - [x] 수익률은 소수점 둘째 자리에서 반올림하여 출력한다.
- [x] 예외 상황 시 에러 문구를 출력한다.
  - [x] 에러 문구는 &quot;[ERROR]&quot;로 시작해야 한다.

---

## 🚨 과제 제출 전 체크 리스트
- [x] 요구 사항에 명시된 출력값 형식을 지켰는지 확인
  - [x] 예외 발생시 `[ERROR]`로 시작하는지 확인
  - [x] 수익률은 소수점 둘째 자리에서 반올림하는지 확인
  - [x] 로또 번호가 오름차순으로 정렬하여 출력하는지 확인
- [x] 모든 테스트가 성공하는지 확인
  - [x] `./gradlew clean test`가 정상 통과하는지 확인
  - [x] `ApplicationTest`가 정상 통과하는지 확인
  - [x] 도메인 로직에 단위 테스트를 구현했는지 확인
- [x] 자바 17버전으로 정상 작동되는지 확인
- [x] 프로그램 실행의 시작점이 Application의 main()인지 확인
- [x] indent depth가 3이 넘지 않는지 확인
- [x] 3항 연산자를 쓰지 않았는지 확인 
- [x] 함수의 길이가 15라인을 넘어가지 않도록 확인
- [x] else 예약어를 쓰지 않았는지 확인
  - [x] switch/case를 쓰지 않았는지 확인
- [x] Java Enum을 적용했는지 확인
- [x] `Randoms` 및 `Console` API를 사용했는지 확인
  - [x] Random 값 추출은 `camp.nextstep.edu.missionutils.Randoms` 사용했는지 확인
  - [x] 입력은 `camp.nextstep.edu.missionutils.Console`의 `readLine()`을 사용했는지 확인
- [x] 로또 클래스 확인
  - [x] 로또 클래스의 numbers의 접근 제어자가 private인지 확인
  - [x] 로또 클래스의 인스턴스 변수를 추가했는지 확인(추가하는 것을 허용하지 않음)</code></pre><p>이건 제가 3주 차 미션을 진행하기 이전 <code>로또 게임 규칙</code>과 <code>구현할 기능 목록</code>을 작성한 내용들입니다.</p>
<p>이전 주차와 달라진 것은, 이전 주차에는 기능 목록에서 클래스명까지 생각하고 작성하였습니다. </p>
<p>하지만 <code>2주 차 공통 피드백</code>에 아래와 같이 글이 작성되어있었습니다.</p>
<blockquote>
<p>기능 목록을 클래스 설계와 구현, 함수(메서드) 설계와 구현과 같이 너무 상세하게 작성하지 않는다. 클래스 이름, 함수(메서드) 시그니처와 반환값은 언제든지 변경될 수 있기 때문이다...</p>
</blockquote>
<p>마치 저의 기능 목록을 보고 공통 피드백을 작성하셨나 느꼈을만큼, 이전까지 기능 목록을 작성하는 방법이 잘못되었다는 것을 알게 되었습니다. </p>
<p>그래서 변경 가능한 클래스명이나 메서드명 등을 최대한 작성하지 않을 수 있도록 신경쓰며 작성해보았습니다.</p>
<hr>
<h2 id="핵심-기능">핵심 기능</h2>
<h3 id="원시값-포장">원시값 포장</h3>
<p><code>보너스 번호</code>에 대한 값을 검증하기 위해 원시값을 포장하여 미션을 진행하였습니다.</p>
<p>이번 미션에서 <code>보너스 번호</code>의 <strong>숫자 범위</strong>는 <code>1~45</code>까지였습니다.
이러한 <code>보너스 번호</code>에 대한 숫자 범위를 쉽게 검증하기 위해 <code>BonusNumber</code>라는 객체를 구현하여 이 안에 검증 관련 코드를 위치시켰습니다.</p>
<pre><code class="language-java">public class BonusNumber {
    private static final String OUT_OF_RANGE_NUMBER_EXCEPTION_FORMAT = &quot;보너스 번호는 %d부터 %d 사이의 숫자여야 합니다.&quot;;
    private static final int MINIMUM_LOTTO_NUMBER = 1;
    private static final int MAXIMUM_LOTTO_NUMBER = 45;

    private final int bonusNumber;

    public BonusNumber(int bonusNumber) {
        validateBonusNumber(bonusNumber);
        this.bonusNumber = bonusNumber;
    }

    private void validateBonusNumber(int bonusNumber) {
        if (isOutOfRange(bonusNumber)) {
            throw new IllegalArgumentException(
                    String.format(OUT_OF_RANGE_NUMBER_EXCEPTION_FORMAT, MINIMUM_LOTTO_NUMBER, MAXIMUM_LOTTO_NUMBER));
        }
    }

    private boolean isOutOfRange(int bonusNumber) {
        return bonusNumber &lt; MINIMUM_LOTTO_NUMBER || bonusNumber &gt; MAXIMUM_LOTTO_NUMBER;
    }
}</code></pre>
<blockquote>
<p>물론, <code>기본 로또 번호</code> 또한 <strong>숫자 범위</strong>는 <code>1~45</code>까지였지만 위에서 말씀드린대로 주어진 <code>Lotto</code> 클래스를 이용하여 구현해야되었기 때문에 로또 번호에 대한 원시값 포장은 따로 진행하지 않고, 로또 번호는 단순히 <code>List&lt;Integer&gt;</code> 타입을 이용하여 구현하였습니다.</p>
</blockquote>
<p>이처럼 우아한테크코스 프리코스를 진행하며 느꼈던 것 중 하나는, 주어진 요구 사항을 읽다 보면 이 부분은 <code>원시값을 포장하여 미션을 진행하면 좋겠다라는 생각</code>을 자연스레 느낄 수 있었습니다!! 👍</p>
<hr>
<h3 id="검증">검증</h3>
<p>이번 미션에서 <code>예외적인 부분</code>을 계속해서 생각해보고, 작성해보았습니다.</p>
<p>이를 통해, 추려낼 수 있었던 검증 부분들은 다음과 같습니다.</p>
<h4 id="1-핵심-도메인-검증">1. 핵심 도메인 검증</h4>
<ul>
<li>로또 검증 기능<ul>
<li><input checked="" disabled="" type="checkbox"> <strong>[예외 처리]</strong> 로또 번호의 개수가 6개가 아니면 예외가 발생한다.</li>
<li><input checked="" disabled="" type="checkbox"> <strong>[예외 처리]</strong> 로또 번호의 숫자들 중 1보다 작거나 45보다 큰 숫자가 있다면 예외가 발생한다.</li>
<li><input checked="" disabled="" type="checkbox"> <strong>[예외 처리]</strong> 6개의 숫자 중 중복되는 숫자가 있다면 예외가 발생한다.</li>
</ul>
</li>
<li>로또 구입 금액 검증 기능<ul>
<li><input checked="" disabled="" type="checkbox"> <strong>[예외 처리]</strong> 로또 구입 금액이 1,000원보다 작다면 예외가 발생한다.</li>
<li><input checked="" disabled="" type="checkbox"> <strong>[예외 처리]</strong> 로또 구입 금액이 1,000원으로 나누어 떨어지지 않으면 예외가 발생한다.</li>
</ul>
</li>
<li>당첨 로또 검증 기능<ul>
<li><input checked="" disabled="" type="checkbox"> <strong>[예외 처리]</strong> 6개의 숫자 중 보너스 번호와 일치하는 숫자가 있으면 예외가 발생한다.</li>
</ul>
</li>
</ul>
<hr>
<h4 id="2-올바른-입력-검증">2. 올바른 입력 검증</h4>
<ul>
<li><input checked="" disabled="" type="checkbox"> <strong>[공통 예외 처리]</strong> 입력이 공백이면 예외가 발생한다.</li>
<li><input checked="" disabled="" type="checkbox"> 로또 구입 금액을 입력 받는다.<ul>
<li><input checked="" disabled="" type="checkbox"> <strong>[예외 처리]</strong> 로또 구입 금액에 대한 입력이 숫자가 아니면 예외가 발생한다.</li>
</ul>
</li>
<li><input checked="" disabled="" type="checkbox"> 당첨 번호를 입력 받는다.<ul>
<li><input checked="" disabled="" type="checkbox"> <strong>[예외 처리]</strong> 당첨 번호에 대한 입력 형식이 올바르지 않으면 예외가 발생한다.</li>
</ul>
</li>
<li><input checked="" disabled="" type="checkbox"> 보너스 번호를 입력 받는다.<ul>
<li><input checked="" disabled="" type="checkbox"> <strong>[예외 처리]</strong> 보너스 번호에 대한 입력이 숫자가 아니면 예외가 발생한다.</li>
</ul>
</li>
</ul>
<blockquote>
<p>다음과 같이 검증 처리를 핵심 <code>도메인 부분</code>과 <code>입력 부분</code>으로 나눈 이유는, 제 개인적인 생각으로 검증 처리는 <code>view 영역</code>과 <code>domain 영역</code> 모든 부분에서 하는 것이 옳다고 느껴졌기 때문입니다. <code>view 영역</code>에서는 입력 값이 올바른지 판단하기 위해 단순한 검증만 진행하고, <code>domain 영역</code>에서 핵심적인 검증 기능을 수행할 수 있도록 구현하였습니다.</p>
</blockquote>
<hr>
<h3 id="enum-활용">Enum 활용</h3>
<p>이번 미션에서 <strong>추가된 요구 사항</strong> 중 <code>Java Enum을 적용한다.</code>라는 요구 사항이 있었습니다.</p>
<p>Enum 객체를 활용하기 위해 <code>로또 등수</code>에 대한 값을 Enum을 활용하여 구현해볼 수 있었습니다.</p>
<pre><code class="language-java">public enum LottoRanking {
    FIRST(6, false, 2000000000),
    SECOND(5, true, 30000000),
    THIRD(5, false, 1500000),
    FOURTH(4, false, 50000),
    FIFTH(3, false, 5000),
    NOTHING(0, false, 0);

    private static final int VALUE_TO_DETERMINE_SECOND_OR_THIRD = 5;

    private final int numberOfMatches;
    private final boolean hasBonusNumber;
    private final int prizeMoney;

    LottoRanking(int numberOfMatches, boolean hasBonusNumber, int prizeMoney) {
        this.numberOfMatches = numberOfMatches;
        this.hasBonusNumber = hasBonusNumber;
        this.prizeMoney = prizeMoney;
    }

    public static LottoRanking of(int numberOfMatches, boolean hasBonusNumber) {
        if (numberOfMatches != VALUE_TO_DETERMINE_SECOND_OR_THIRD) {
            return findRanking(numberOfMatches, false);
        }
        return findRanking(numberOfMatches, hasBonusNumber);
    }

    private static LottoRanking findRanking(int numberOfMatches, boolean hasBonusNumber) {
        return Arrays.stream(values())
                .filter(lottoRanking -&gt; lottoRanking.isMatch(numberOfMatches, hasBonusNumber))
                .findFirst()
                .orElse(NOTHING);
    }

    private boolean isMatch(int numberOfMatches, boolean hasBonusNumber) {
        return this.numberOfMatches == numberOfMatches &amp;&amp; this.hasBonusNumber == hasBonusNumber;
    }
}</code></pre>
<p>다음과 같이 로또와 일치하는 숫자 개수와 보너스 번호 일치 여부를 이용하여 로또 등수를 매길 수 있도록 구현하였습니다.</p>
<p>여기서 주의한 점은, 보너스 번호 일치 여부는 2등과 3등일 경우만 이를 구분하기 위해 필요하고, 다른 경우는 보너스 번호 일치 여부는 중요하지 않습니다.</p>
<p>이를 위해, 다음과 같이 구현해볼 수 있었습니다.</p>
<pre><code class="language-java">public static LottoRanking of(int numberOfMatches, boolean hasBonusNumber) {
    if (numberOfMatches != VALUE_TO_DETERMINE_SECOND_OR_THIRD) {
        return findRanking(numberOfMatches, false);
    }
    return findRanking(numberOfMatches, hasBonusNumber);
}</code></pre>
<p>로또와 일치하는 숫자 개수가 5가 아닐 경우는 findRanking() 메서드에 false 값을 전달하고, 맞을 경우는 전달된 hasBonusNumber 값을 그대로 사용하여 로또 등수를 구하도록 구현하였습니다.</p>
<hr>
<h3 id="상수-활용">상수 활용</h3>
<p>2주 차 공통 피드백에서 다음과 같은 말이 명시되어 있었다.</p>
<blockquote>
<p>문자열, 숫자 등의 값을 하드 코딩하지 마라. 상수(static final)를 만들고 이름을 부여해 이 변수의 역할이 무엇인지 의도를 드러내라...</p>
</blockquote>
<p>이를 통해 이전까지는 예외 메시지 등을 상수로 처리하지 않았더라면, 이번 미션에서는 모든 예외 메시지까지 상수로 처리할 수 있도록 구현하였습니다.</p>
<pre><code class="language-java">// 로또 클래스 예외 메시지 예시
private static final String INVALID_NUMBERS_SIZE_EXCEPTION_FORMAT = &quot;로또는 총 %d개의 번호로 이루어져야 합니다.&quot;;
private static final String OUT_OF_RANGE_NUMBER_EXCEPTION_FORMAT = &quot;로또 번호는 %d부터 %d 사이의 숫자여야 합니다.&quot;;
private static final String DUPLICATED_NUMBER_EXCEPTION_MESSAGE = &quot;로또 번호들 중 중복된 숫자가 존재합니다.&quot;;</code></pre>
<hr>
<h3 id="테스트-코드">테스트 코드</h3>
<p>모든 도메인 로직에 대한 테스트 코드를 작성하기 위해 노력했습니다.</p>
<p>이전 미션에서 테스트 코드를 통해 <strong><em>오류를 쉽게 찾을 수 있었다는 장점</em></strong>을 느꼈다면, 이번 미션에서는 리팩토링 과정에서 여러 구현 방법을 적용하면서, <strong><em>놓친 부분들을 테스트 코드의 오류를 통해 쉽게 감지할 수 있다는 장점</em></strong>을 느낄 수 있었습니다.
<img src="https://velog.velcdn.com/images/jcoding-play/post/570fd6ed-32f1-4507-852c-b7af60b7ae05/image.png" alt=""></p>
<hr>
<h2 id="마치며">마치며</h2>
<p>벌써 절반 이상을 진행하며 벌써 아쉽기도 하며, 한편으로 목표를 가지고 성장할 수 있는 기회를 준 우테코에게 감사의 말을 전하고 싶다!! 😀</p>
<blockquote>
<p>저의 자세한 코드가 궁금하신 분은 아래 링크에서 확인할 수 있습니다.
<a href="https://github.com/jcoding-play/java-lotto-6/tree/gyungchan">https://github.com/jcoding-play/java-lotto-6/tree/gyungchan</a></p>
</blockquote>
<hr>
<h1 id="최종-리팩토링-후-달라진-부분">최종 리팩토링 후 달라진 부분</h1>
<p>이 부분은 프리코스가 끝나고 난 뒤 리팩토링을 진행하고 이전과 크게 달라진 점을 알려드리기 위해 작성해보았습니다!!</p>
<h2 id="원시값-포장-1">원시값 포장</h2>
<p>이번 미션에서는 제공된 <code>Lotto 클래스</code>를 활용하도록 명시되어 있었기 때문에, <code>로또 번호</code>에 대한 원시값 포장을 진행하지 못하였었다.</p>
<p>하지만 지금은 혼자 리팩토링을 진행하는 것이기에 로또 번호에 대한 값도 <code>LottoNumber</code>라는 객체를 이용하여 원시값을 포장하도록 구현하였다.</p>
<pre><code class="language-java">public class Lotto {
    // 바뀐 부분
    private final List&lt;LottoNumber&gt; numbers;

    public Lotto(List&lt;Integer&gt; numbers) {
        validate(numbers);
        this.numbers = mapLottoNumber(numbers);
    }

    private List&lt;LottoNumber&gt; mapLottoNumber(List&lt;Integer&gt; numbers) {
        return numbers.stream()
                .map(LottoNumber::new)
                .toList();
    }

    ...
}</code></pre>
<pre><code class="language-java">public class LottoNumber {
    private static final int MINIMUM_LOTTO_NUMBER = 1;
    private static final int MAXIMUM_LOTTO_NUMBER = 45;

    private final int lottoNumber;

    public LottoNumber(int lottoNumber) {
        validateLottoNumber(lottoNumber);
        this.lottoNumber = lottoNumber;
    }

    private void validateLottoNumber(int lottoNumber) {
        if (lottoNumber &lt; MINIMUM_LOTTO_NUMBER || lottoNumber &gt; MAXIMUM_LOTTO_NUMBER) {
            throw new IllegalArgumentException(
                    String.format(&quot;로또 번호는 %d부터 %d 사이의 숫자여야 합니다.&quot;, MINIMUM_LOTTO_NUMBER, MAXIMUM_LOTTO_NUMBER));
        }
    }
}</code></pre>
<p>또한 <code>로또 구입 금액에 대한 값</code>도 원시값을 포장하여 구현할 수 있도록 리팩토링하였다.</p>
<p>이전에는 로또 구입 금액에 대한 값을 검증하기 위해 <code>LottoStore</code> 클래스에 buyLotto 메서드 내부에서 검증을 진행하였다.</p>
<pre><code class="language-java">// 리팩토링 전 LottoStore 클래스
public class LottoStore {
    private static final String LESS_THAN_MINIMUM_PRICE_EXCEPTION_FORMAT = &quot;로또 구입 금액은 최소 %d원 이상이어야 합니다.&quot;;
    private static final String INVALID_PURCHASE_AMOUNT_EXCEPTION_FORMAT = &quot;로또 구입 금액은 %d원 단위어야 합니다.&quot;;

    private static final int VALID_REMAINING_AMOUNT = 0;

    private final LottoGenerator lottoGenerator;

    public LottoStore(LottoGenerator lottoGenerator) {
        this.lottoGenerator = lottoGenerator;
    }

    public List&lt;Lotto&gt; buyLotto(int purchaseAmount) {
        validatePurchaseAmount(purchaseAmount);

        return lottoGenerator.createLottos(purchaseAmount / LOTTO_PRICE);
    }

    private void validatePurchaseAmount(int purchaseAmount) {
        if (purchaseAmount &lt; LOTTO_PRICE) {
            throw new IllegalArgumentException(
                    String.format(LESS_THAN_MINIMUM_PRICE_EXCEPTION_FORMAT, LOTTO_PRICE));
        }
        if (isInvalidPurchaseAmount(purchaseAmount)) {
            throw new IllegalArgumentException(
                    String.format(INVALID_PURCHASE_AMOUNT_EXCEPTION_FORMAT, LOTTO_PRICE));
        }
    }

    private boolean isInvalidPurchaseAmount(int purchaseAmount) {
        return purchaseAmount % LOTTO_PRICE != VALID_REMAINING_AMOUNT;
    }
}</code></pre>
<p>하지만 이를 다음과 같이 <code>PurchaseAmount</code>라는 객체를 이용하여 구입 금액에 대한 검증 기능을 위치시키고, <strong>LottoStore 클래스 내부에서는 이와 관련된 기능만을 수행하도록 리팩토링</strong>하였다.</p>
<pre><code class="language-java">public class PurchaseAmount {
    private final int purchaseAmount;

    public PurchaseAmount(int purchaseAmount) {
        validatePurchaseAmount(purchaseAmount);
        this.purchaseAmount = purchaseAmount;
    }

    private void validatePurchaseAmount(int purchaseAmount) {
        if (purchaseAmount &lt; Constants.LOTTO_PRICE) {
            throw new IllegalArgumentException(
                    String.format(&quot;구입 금액은 최소 %,d원 이상이어야 합니다.&quot;, Constants.LOTTO_PRICE));
        }
        if (isInvalidPurchaseAmount(purchaseAmount)) {
            throw new IllegalArgumentException(
                    String.format(&quot;구입 금액은 %,d원 단위어야 합니다.&quot;, Constants.LOTTO_PRICE));
        }
    }
}</code></pre>
<pre><code class="language-java">// 리팩토링 후 LottoStore 클래스
public class LottoStore {

    public Lottos buyLotto(PurchaseAmount purchaseAmount) {
        int result = purchaseAmount.divideByLottoPrice();
        return generateLottos(result);
    }

    private Lottos generateLottos(int size) {
        return Stream.generate(this::generateLotto)
                .limit(size)
                .collect(Collectors.collectingAndThen(Collectors.toList(), Lottos::new));
    }

    private Lotto generateLotto() {
        return new Lotto(pickUniqueNumbers());
    }

    private List&lt;Integer&gt; pickUniqueNumbers() {
        return Randoms.pickUniqueNumbersInRange(MINIMUM_LOTTO_NUMBER, MAXIMUM_LOTTO_NUMBER, LOTTO_NUMBERS_SIZE);
    }
}</code></pre>
<hr>
<h2 id="함수형-인터페이스-활용">함수형 인터페이스 활용</h2>
<p>이전에 로또 등수를 구하기 위해 if 문을 이용하여 구할 수 있었다.</p>
<p>이를 깔끔하게 변경하기 위한 방법을 찾아보다가 BiPredicate라는 함수형 인터페이스를 적용하게 되었습니다.</p>
<pre><code class="language-java">@FunctionalInterface
public interface BiPredicate&lt;T, U&gt; {
    boolean test(T t, U u);
}    </code></pre>
<p>이는 2개의 인자를 통해 참, 거짓을 반환해주는 함수형 인터페이스입니다. </p>
<p>BiPredicate을 적용함으로 if문을 이용하여 등수를 구했던 이전과 달리 간단히 로또 등수를 구할 수 있었습니다.</p>
<pre><code class="language-java">// 변경 전 LottoRanking
public enum LottoRanking {
    FIRST(6, false, 2000000000),
    SECOND(5, true, 30000000),
    THIRD(5, false, 1500000),
    FOURTH(4, false, 50000),
    FIFTH(3, false, 5000),
    NOTHING(0, false, 0);

    private static final int VALUE_TO_DETERMINE_SECOND_OR_THIRD = 5;

    private final int numberOfMatches;
    private final boolean hasBonusNumber;
    private final int prizeMoney;

    LottoRanking(int numberOfMatches, boolean hasBonusNumber, int prizeMoney) {
        this.numberOfMatches = numberOfMatches;
        this.hasBonusNumber = hasBonusNumber;
        this.prizeMoney = prizeMoney;
    }

    public static LottoRanking of(int numberOfMatches, boolean hasBonusNumber) {
        if (numberOfMatches != VALUE_TO_DETERMINE_SECOND_OR_THIRD) {
            return findRanking(numberOfMatches, false);
        }
        return findRanking(numberOfMatches, hasBonusNumber);
    }

    private static LottoRanking findRanking(int numberOfMatches, boolean hasBonusNumber) {
        return Arrays.stream(values())
                .filter(lottoRanking -&gt; lottoRanking.isMatch(numberOfMatches, hasBonusNumber))
                .findFirst()
                .orElse(NOTHING);
    }

    private boolean isMatch(int numberOfMatches, boolean hasBonusNumber) {
        return this.numberOfMatches == numberOfMatches &amp;&amp; this.hasBonusNumber == hasBonusNumber;
    }
}</code></pre>
<pre><code class="language-java">// 변경 후 LottoRanking
public enum LottoRanking {
    FIRST((numberOfMatches, hasBonusNumber) -&gt; numberOfMatches == 6, 2_000_000_000),
    SECOND((numberOfMatches, hasBonusNumber) -&gt; numberOfMatches == 5 &amp;&amp; hasBonusNumber, 30_000_000),
    THIRD((numberOfMatches, hasBonusNumber) -&gt; numberOfMatches == 5, 1_500_000),
    FOURTH((numberOfMatches, hasBonusNumber) -&gt; numberOfMatches == 4, 50_000),
    FIFTH((numberOfMatches, hasBonusNumber) -&gt; numberOfMatches == 3, 5_000),
    NOTHING((numberOfMatches, hasBonusNumber) -&gt; numberOfMatches == 0, 0);

    private final BiPredicate&lt;Integer, Boolean&gt; predicate;
    private final int prizeMoney;

    LottoRanking(BiPredicate&lt;Integer, Boolean&gt; predicate, int prizeMoney) {
        this.predicate = predicate;
        this.prizeMoney = prizeMoney;
    }

    public static LottoRanking of(int numberOfMatches, boolean hasBonusNumber) {
        return Arrays.stream(values())
                .filter(lottoRanking -&gt; lottoRanking.isMatch(numberOfMatches, hasBonusNumber))
                .findFirst()
                .orElse(NOTHING);
    }

    private boolean isMatch(int numberOfMatches, boolean hasBonusNumber) {
        return predicate.test(numberOfMatches, hasBonusNumber);
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[우아한테크코스 6기] 프리코스 2주차 회고 && 최종 리팩토링 후기]]></title>
            <link>https://velog.io/@jcoding-play/%EC%9A%B0%EC%95%84%ED%95%9C%ED%85%8C%ED%81%AC%EC%BD%94%EC%8A%A4-6%EA%B8%B0-%ED%94%84%EB%A6%AC%EC%BD%94%EC%8A%A4-2%EC%A3%BC%EC%B0%A8-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@jcoding-play/%EC%9A%B0%EC%95%84%ED%95%9C%ED%85%8C%ED%81%AC%EC%BD%94%EC%8A%A4-6%EA%B8%B0-%ED%94%84%EB%A6%AC%EC%BD%94%EC%8A%A4-2%EC%A3%BC%EC%B0%A8-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Wed, 22 Nov 2023 09:57:26 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>이 글은 프리코스 2주차 회고 글, 그리고 프리코스가 끝난 뒤 진행한 리팩토링 결과까지 포함하고 있다는 것을 미리 말씀드립니다!! 😀
리팩토링 후 달라진 점도 궁금하신 분들을 끝까지 다 봐주세요!~🧐🧐</p>
</blockquote>
<hr>
<p>2주 차 미션에 대한 글을 작성하기 이전에 먼저 <code>1주 차 공통 피드백</code> 내용들은 다음과 같습니다.</p>
<blockquote>
<ul>
<li>요구사항을 정확히 준수한다</li>
</ul>
</blockquote>
<ul>
<li>커밋 메시지를 의미 있게 작성한다</li>
<li>git을 통해 관리할 자원에 대해서도 고려한다</li>
<li>Pull Request를 보내기 전 브랜치를 확인한다</li>
<li>PR을 한 번 작성했다면 닫지 말고 추가 커밋을 한다</li>
<li>이름을 통해 의도를 드러낸다</li>
<li>축약하지 않는다</li>
<li>공백도 코딩 컨벤션이다</li>
<li>공백 라인을 의미 있게 사용한다</li>
<li>space와 tab을 혼용하지 않는다</li>
<li>의미 없는 주석을 달지 않는다</li>
<li>IDE의 코드 자동 정렬 기능을 활용한다</li>
<li>Java에서 제공하는 API를 적극 활용한다</li>
<li>배열 대신 Java Collection을 사용한다</li>
</ul>
<p>1주 차 공통 피드백에 대해 자세히 보고 싶은 분은 <a href="https://docs.google.com/document/d/1cW2jE3UoaBDNvGJ_Uh9zgkZXisOVezkRiPNpZvVmW9w/edit">1주 차 공통 피드백</a>에서 볼 수 있습니다.</p>
<p>2주 차 미션을 구현하기 이전 <code>1주 차 공통 피드백</code> 내용들을 준수할 수 있도록 노력했습니다.</p>
<h1 id="2주차">2주차</h1>
<hr>
<h2 id="자동차-경주">자동차 경주</h2>
<p>2주차 미션은 <code>자동차 경주</code>를 구현하는 것이었습니다.
미션을 진행하는 방식은 다음과 같이 나와있었습니다.</p>
<blockquote>
<p>🔍 진행 방식</p>
</blockquote>
<ul>
<li>미션은 기능 요구 사항, 프로그래밍 요구 사항, 과제 진행 요구 사항 세 가지로 구성되어 있다.</li>
<li>세 개의 요구 사항을 만족하기 위해 노력한다. 특히 기능을 구현하기 전에 기능 목록을 만들고, 기능 단위로 커밋 하는 방식으로 진행한다.</li>
<li>기능 요구 사항에 기재되지 않은 내용은 스스로 판단하여 구현한다.</li>
</ul>
<h4 id="요구-사항-분석">요구 사항 분석</h4>
<hr>
<p>1주 차 공통 피드백에도 나와있듯이, 요구 사항을 정확히 준수하기 위해 노력했습니다.</p>
<p>미션을 진행하기 전에 요구 사항을 꼼꼼히 분석하고 이를 토대로 <code>구현할 기능 목록</code>을 작성하였습니다.</p>
<pre><code>## 🚀 기능 요구 사항

## Converter
- [x] 입력된 문자열을 쉼표(,)를 기준으로 분리할 수 있다.

## RandomNumberGenerator
- [x] 0에서 9사이의 무작위 값을 하나 생성한다.

## Name
- [x] 자동차의 이름을 부여할 수 있다.
  - [x] **[예외 처리]** 자동차의 이름이 1자 미만이거나 5자를 초과하면 예외가 발생한다.

## Position
- [x] 자동차의 위치를 알 수 있다.
  - [x] **[예외 처리]** 자동차의 위치가 0보다 작다면 예외가 발생한다.

## Car
- [x] 자동차는 전진할 수 있다.
  - [x] 입력된 값이 4이상 9이하일 경우 전진할 수 있다.
  - [x] 입력된 값이 0이상 3이하일 경우 전진할 수 없다.
  - [x] **[예외 처리]** 입력된 값이 0이상 9이하가 아니라면 예외가 발생한다.

## Cars
- [x] 경주에 참여할 자동차들의 정보를 알 수 있다.
  - [x] **[예외 처리]** 경주에 참여할 자동차가 없다면 예외가 발생한다.
  - [x] **[예외 처리]** 경주에 참여하는 자동차의 이름 중 중복되는 이름이 있다면 예외가 발생한다.
- [x] 경주에 참여하는 자동차들이 전진 또는 멈출 수 있다.
- [x] 경주에 참여하는 자동차들 중 가장 많이 전진한 자동차를 알 수 있다.
  - [x] 가장 많이 전진한 자동차는 여러대일 수 있다.

## RaceCount
- [x] 경주를 진행할 횟수를 알 수 있다.
  - [x] **[예외 처리]** 경주를 진행할 횟수가 1보다 작다면 예외가 발생한다.

## RacingGame
- [x] 자동차 경주를 진행할 수 있다.
  - [x] 경주를 한번 진행할때마다 진행할 횟수가 1씩 줄어든다.
- [x] 자동차 경주 게임에 대한 종료 여부를 알 수 있다.
  - [x] 경주를 진행할 횟수가 0이면 게임이 종료된다.
- [x] 자동차 경주 게임의 우승자를 알 수 있다.
  - [x] 우승자는 여러 명일 수 있다.
  - [x] **[예외 처리]** 자동차 경주 게임이 끝나지 않은 상태에서 우승자를 찾으려 하면 예외가 발생한다.

## 입력
- [x] **[공통 예외 처리]** 입력이 공백이면 예외가 발생한다.
- [x] 경주 할 자동차의 이름을 쉼표(,)를 기준으로 입력한다.
- [x] 경주를 진행할 횟수를 입력한다.
  - [x] **[예외 처리]** 경주를 진행할 횟수에 대한 입력이 숫자가 아니라면 예외가 발생한다.
- [x] 사용자가 잘못된 값을 입력할 경우 `IllegalArgumentException`을 발생시킨 후 애플리케이션은 종료되어야 한다.

## 출력
- [x] 실행 결과 메시지를 출력한다.
- [x] 각 차수별 실행 결과를 출력한다.
  - [x] 자동차의 위치는 &#39;-&#39; 문자로 표시한다.
- [x] 자동차 경주 게임의 우승자를 출력한다.
  - [x] 우승자가 여러 명일 경우 쉼표(,)를 이용하여 구분한다.</code></pre><p>이건 제가 2주 차 미션을 진행하기 이전 <code>구현할 기능 목록</code>을 작성한 내용들입니다.</p>
<p>특히 예외 상황에 대해 고민해보고 작성하려 노력했습니다.</p>
<hr>
<h2 id="핵심-기능">핵심 기능</h2>
<h3 id="원시값-포장">원시값 포장</h3>
<p>저번 미션에서 원시값 포장을 하여 이점을 느낀만큼, 이번 미션에서도 원시값을 포장하려고 노력했습니다.</p>
<blockquote>
<p>이번 미션에서 원시값을 포장한 한가지 예시를 작성해보겠습니다.</p>
</blockquote>
<p><code>Car</code>라는 객체는 각 <code>이름</code>과 <code>위치</code>에 대한 값을 가지고 있어야 했고, <strong>이름은 5자 이하만 가능</strong>하다는 요구 사항이 있었습니다. 또한 <strong>위치 값은 0보다 작은 값이어서는 안된다</strong>는 것을 요구 사항을 통해 도출할 수 있었습니다.</p>
<p>이를 해결하기 위해, 다음과 같이 원시값을 포장하였습니다.</p>
<pre><code class="language-java">public class Name {
    private static final int MINIMUM_LENGTH_OF_NAME = 1;
    private static final int MAXIMUM_LENGTH_OF_NAME = 5;

    private final String name;

    public Name(String name) {
        validateName(name);
        this.name = name;
    }

    private void validateName(String name) {
        int length = name.length();

        if (length &lt; MINIMUM_LENGTH_OF_NAME || length &gt; MAXIMUM_LENGTH_OF_NAME) {
            throw new IllegalArgumentException(
                    String.format(&quot;자동차의 이름은 %d자 이상, %d자 이하여야 합니다.&quot;, MINIMUM_LENGTH_OF_NAME, MAXIMUM_LENGTH_OF_NAME)
            );
        }
    }
}</code></pre>
<pre><code class="language-java">public class Position implements Comparable&lt;Position&gt; {
    private static final int MINIMUM_POSITION = 0;

    private int position;

    public Position(int position) {
        validatePosition(position);
        this.position = position;
    }

    private void validatePosition(int position) {
        if (position &lt; MINIMUM_POSITION) {
            throw new IllegalArgumentException(
                    String.format(&quot;자동차의 위치는 %d보다 작을 수 없습니다.&quot;, MINIMUM_POSITION)
            );
        }
    }
}</code></pre>
<p>이를 통해, <code>Car</code> 객체는 이름과 위치 값에 대한 검증 책임을 갖지 않도록 구현할 수 있었습니다.</p>
<hr>
<h3 id="comparable-인터페이스-활용">Comparable 인터페이스 활용</h3>
<p>이번 미션에서, 핵심적인 기능 중 하나라고 할 수 있는 <code>우승자를 찾는 기능</code>을 <code>Comparable 인터페이스</code>를 활용하여 구현하였습니다.</p>
<blockquote>
<p>Comparable 인터페이스는 객체간의 비교를 가능하게 해주는 인터페이스이다. compareTo 메서드를 정의하여 사용 가능하다.</p>
</blockquote>
<p>먼저, Car와 Position 객체에 Comparable 인터페이스의 compareTo 메서드를 정의하였습니다.</p>
<pre><code class="language-java">// Position 객체 내부 compareTo 메서드
@Override
public int compareTo(Position other) {
    return this.position - other.position;
}

// Car 객체 내부 compareTo 메서드
@Override
public int compareTo(Car other) {
    return position.compareTo(other.position);
}</code></pre>
<p>다음과 같이 구현하여 Position의 값을 통해 객체간의 비교를 할 수 있게 만들 수 있습니다.</p>
<p>그래서, <code>List&lt;Car&gt;</code> 중에서 가장 큰 위치 값을 가지는 <code>Car</code> 객체를 아래 코드와 같이 쉽게 구현할 수 있었습니다.</p>
<pre><code class="language-java">private Car findMaxPositionCar() {
    return cars.stream()
        .max(Car::compareTo)
        .get();
}</code></pre>
<hr>
<h3 id="stream-활용">Stream 활용</h3>
<p>이번 미션에서 <code>Stream</code>을 최대한 사용해보려고 노력해보았습니다.</p>
<ul>
<li>중복된 이름이 있는지 판단하기 위한 기능</li>
</ul>
<pre><code class="language-java">private boolean hasDuplicatedName(List&lt;Car&gt; cars) {
    return cars.stream()
        .map(Car::getName)
        .distinct()
        .count() != cars.size();
}</code></pre>
<ul>
<li>가장 많이 전진해있는 자동차들을 찾는 기능</li>
</ul>
<pre><code class="language-java">private List&lt;Car&gt; findMostMovedCars(Car maxPositionCar) {
    return cars.stream()
        .filter(car -&gt; car.isSamePosition(maxPositionCar))
        .toList();
}</code></pre>
<ul>
<li>가장 많이 전진해있는 자동차 하나를 찾는 기능</li>
</ul>
<pre><code class="language-java">private Car findMaxPositionCar() {
    return cars.stream()
        .max(Car::compareTo)
        .get();
}</code></pre>
<ul>
<li>우승자를 찾는 기능</li>
</ul>
<pre><code class="language-java">private List&lt;String&gt; findWinners(List&lt;Car&gt; mostMovedCars) {
    return mostMovedCars.stream()
        .map(Car::getName)
        .toList();
}</code></pre>
<ul>
<li>각 차수별 자동차들의 이동 결과 메시지를 생성하는 기능</li>
</ul>
<pre><code class="language-java">private String generateRaceResultMessage(List&lt;CarDto&gt; cars) {
    return cars.stream()
        .map(this::generateMessageOf)
        .collect(Collectors.joining(NEWLINE));
}</code></pre>
<p>이번 프리코스 미션을 통해 조금씩 Stream의 여러 기능을 실행해 볼 수 있어서 좋은 기회였다고 더 느낄 수 있었습니다.</p>
<hr>
<h3 id="테스트를-쉽게-작성하기-위한-인터페이스-활용">테스트를 쉽게 작성하기 위한 인터페이스 활용</h3>
<p>만약 메서드 내부에 <code>랜덤 값을 생성하는 기능</code>이 있고, 생성된 랜덤 값을 통해 다른 역할을 수행하는 메서드가 있다면 테스트를 어떻게 작성해야 할까요?? </p>
<p>이번 미션에서도 경주에 참여하는 자동차들을 이동시키기 위해 랜덤 값을 생성하고 <code>랜덤값이 4 이상</code>일 경우 자동차들을 이동시켜야 하는 요구 사항이 있었습니다.</p>
<p>이를 테스트를 작성하기 쉽게 인터페이스를 활용하였습니다.</p>
<pre><code class="language-java">@FunctionalInterface
public interface NumberGenerator {

    int generate();
}</code></pre>
<pre><code class="language-java">@Test
@DisplayName(&quot;경주에 참여하는 자동차들을 전진시킬 수 있다.&quot;)
void move() {
    cars.makeMoveOrStop(() -&gt; 4); // 인터페이스를 활용한 부분

    assertThat(cars).extracting(&quot;cars&quot;, InstanceOfAssertFactories.list(Car.class))
            .containsExactly(
                    new Car(&quot;pobi&quot;, 1),
                    new Car(&quot;woni&quot;, 1),
                    new Car(&quot;jun&quot;, 1));
}

@Test
@DisplayName(&quot;경주에 참여하는 자동차들을 정지시킬 수 있다.&quot;)
void stop() {
    cars.makeMoveOrStop(() -&gt; 3); // 인터페이스를 활용한 부분

    assertThat(cars).extracting(&quot;cars&quot;, InstanceOfAssertFactories.list(Car.class))
            .containsExactly(
                    new Car(&quot;pobi&quot;, 0),
                    new Car(&quot;woni&quot;, 0),
                    new Car(&quot;jun&quot;, 0));
}</code></pre>
<p>다음과 같이 랜덤 값을 통해 테스트를 진행하는 것이 아니라, 특정 값을 통해 테스트를 진행함으로써 원하는 결과를 얻을 수 있었습니다.</p>
<hr>
<h3 id="테스트-코드">테스트 코드</h3>
<p>모든 도메인 로직에 대한 테스트 코드를 작성하기 위해 노력했습니다.</p>
<p>또한, 테스트 코드를 통해 여러 발생 가능한 예외 상황에 대해 미리 확인할 수 있었다는 이점을 느낄 수 있었습니다.</p>
<p><img src="https://velog.velcdn.com/images/jcoding-play/post/334d58de-d66a-4fc2-a4a0-c9aff2432edb/image.png" alt=""></p>
<hr>
<h2 id="리팩토링">리팩토링</h2>
<p>요구 사항을 토대로 모든 기능을 구현하고 난 뒤에는 리팩토링에 많은 시간을 할여하였습니다.</p>
<p>분리 가능한 클래스와 메서드가 있는지 판단하며 분리하기도 하였고, 각각의 네이밍이 의도가 명확히 드러나는지를 판단하여 아니라면 이름을 수정하기도 하였습니다.</p>
<p>또한, 1주 차 공통 피드백에도 명시된 듯이 <code>IDE의 코드 자동 정렬 기능을 활용</code>하였습니다.</p>
<p>아직까지도, 부족한 부분들이 많을 것이라 생각하고 계속해서 리팩토링을 진행할 예정입니다.</p>
<blockquote>
<p>리팩토링을 진행하고 변경된 부분이 있으면 다시 블로그 글을 작성하여 나타내보겠습니다.</p>
</blockquote>
<hr>
<h2 id="마치며">마치며</h2>
<p><code>우아한테크코스 프리코스가 좋은 과정인가?</code> 라는 생각을 가지는 분들이 혹시 계시다면 저는 무조건 <code>좋다!</code>라고 대답을 드리고 싶고, 추천드리고 싶습니다.</p>
<p>미션을 진행하는 과정이 너무나 재밌고, 유익하다 말씀드리고 싶습니다.</p>
<blockquote>
<p>저의 자세한 코드가 궁금하신 분은 아래 링크에서 확인할 수 있습니다.
<a href="https://github.com/jcoding-play/java-racingcar-6/tree/gyungchan">https://github.com/jcoding-play/java-racingcar-6/tree/gyungchan</a></p>
</blockquote>
<hr>
<h1 id="최종-리팩토링-후-달라진-부분">최종 리팩토링 후 달라진 부분</h1>
<p>이 부분은 프리코스가 끝나고 난 뒤 리팩토링을 진행하고 이전과 크게 달라진 점을 알려드리기 위해 작성해보았습니다!!</p>
<h2 id="자동차-경주-구현-메서드">자동차 경주 구현 메서드</h2>
<p>리팩토링 전엔 자동차 경주에 대한 결과를 출력하기 위해 <code>자동차 경주 한번 시작 -&gt; 결과 출력 -&gt; 자동차 경주 한번 시작 -&gt; 결과 출력 ...</code> 과 같은 순서로 미션을 진행하였습니다.</p>
<pre><code class="language-java">public class MainController {
    ...

    private void race(RacingGame racingGame, NumberGenerator numberGenerator) {
        while (canRace(racingGame)) {
            racingGame.race(numberGenerator);

            List&lt;CarDto&gt; cars = toCarDto(racingGame.getCars());
            outputView.printRaceResult(cars);
        }
    }

    private boolean canRace(RacingGame racingGame) {
        return !racingGame.isGameEnd();
    }

    private List&lt;CarDto&gt; toCarDto(List&lt;Car&gt; cars) {
        Converter&lt;List&lt;Car&gt;, List&lt;CarDto&gt;&gt; converter = new CarToDtoConverter();
        return converter.convert(cars);
    }

    ...
}</code></pre>
<pre><code class="language-java">public class RacingGame {
    ...

    public void race(NumberGenerator numberGenerator) {
        cars.makeMoveOrStop(numberGenerator);

        raceCount.decrease();
    }

    ...
}</code></pre>
<p>이를 <code>모든 자동차 경주 시작 -&gt; 결과 출력</code>과 같은 순서로 리팩토링 해보았습니다.</p>
<pre><code class="language-java">public class MainController {
    ...

    private void race(RacingGame racingGame) {
        RaceResult raceResult = racingGame.race(new RandomNumberGenerator());
        List&lt;MovingResult&gt; movingResults = raceResult.getMovingResults();

        outputView.printResult(movingResults);
    }

    ...
}</code></pre>
<pre><code class="language-java">public class RacingGame {
    ...

    public RaceResult race(NumberGenerator numberGenerator) {
        List&lt;MovingResult&gt; movingResults = new LinkedList&lt;&gt;();

        while (canPlay()) {
            MovingResult movingResult = cars.makeMoveOrStop(numberGenerator);
            movingResults.add(movingResult);

            movingCount.decrease();
        }

        return new RaceResult(movingResults);
    }

    ...
}</code></pre>
<p>이를 위해, 자동차들이 한번 움직일 때마다 이를 <code>MovingResult</code> 객체에 기록하고, 전체 경주 결과를 <code>RaceResult</code> 객체 안에 기록하여 경주 결과를 한번에 출력할 수 있도록 리팩토링하였습니다.</p>
<blockquote>
<p>큰 차이가 없다고 느끼실 수도 있겠지만, 여러 방법으로 구현해보고 이를 경험해보는 것도 조금씩 성장할 수 있는 발판을 마련해준다 생각합니다!! 😀 </p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Stream의 Collectors]]></title>
            <link>https://velog.io/@jcoding-play/Stream%EC%9D%98-Collectors</link>
            <guid>https://velog.io/@jcoding-play/Stream%EC%9D%98-Collectors</guid>
            <pubDate>Wed, 01 Nov 2023 13:39:45 GMT</pubDate>
            <description><![CDATA[<h2 id="collectors">Collectors</h2>
<p>새롭게 알게된 Stream의 Collectors 기능을 적어보겠습니다.</p>
<pre><code class="language-java">List&lt;Integer&gt; numbers = List.of(1, 2, 3, 1);</code></pre>
<p>위의 numbers 변수를 통해 다양한 기능을 학습해보겠습니다.</p>
<h3 id="tolist">toList</h3>
<hr>
<h4 id="collectorstolist">Collectors.toList()</h4>
<pre><code class="language-java">@Test
void toList() {
    List&lt;String&gt; result = numbers.stream()
                .map(String::valueOf)
                .collect(Collectors.toList());

    assertThat(result).containsExactly(&quot;1&quot;, &quot;2&quot;, &quot;3&quot;, &quot;1&quot;);
}</code></pre>
<p>가장 기본적인 형태입니다.
각각의 숫자들을 <code>String</code>으로 변환하여 변환된 값들을 <code>List&lt;String&gt;</code>으로 반환해줍니다.</p>
<hr>
<h4 id="collectorstocollection">Collectors.toCollection()</h4>
<p>만약 구체적인 클래스인 <code>LinkedList</code>로 반환받고 싶으면 어떻게 해야될까요??
이때는 <strong>toCollection()</strong> 메서드를 사용하면 됩니다.</p>
<p>코드를 통해 보겠습니다.</p>
<pre><code class="language-java">@Test
void toCollection() {
    LinkedList&lt;String&gt; result = numbers.stream()
                .map(String::valueOf)
                .collect(Collectors.toCollection(LinkedList::new));

    assertThat(result).containsExactly(&quot;1&quot;, &quot;2&quot;, &quot;3&quot;, &quot;1&quot;);
}</code></pre>
<p><code>toCollection(LinkedList::new)</code>를 통해 LinkedList로 반환받을 수 있었습니다.</p>
<hr>
<h4 id="collectorstounmodifiablelist">Collectors.toUnmodifiableList()</h4>
<p>또한 불변 List로 반환받을 수 있습니다.</p>
<pre><code class="language-java">List&lt;Integer&gt; result = numbers.stream()
               .collect(Collectors.toUnmodifiableList());</code></pre>
<p>당연히 불변이기 때문에 반환된 List의 내부 값을 변경하려고 하면 <strong>예외가 발생</strong>합니다.</p>
<pre><code class="language-java">@Test
void toUnmodifiableList() {
    List&lt;Integer&gt; result = numbers.stream()
                .collect(Collectors.toUnmodifiableList());

    assertThatThrownBy(() -&gt; result.add(10))
                .isInstanceOf(UnsupportedOperationException.class);
}</code></pre>
<hr>
<h3 id="toset">toSet</h3>
<p><code>Set</code>으로 바꾸기 위한 방법 또한 <code>List</code>와 유사합니다.</p>
<blockquote>
<ul>
<li>Collectors.toSet()</li>
</ul>
</blockquote>
<ul>
<li>Collectors.toCollection(LinkedHashSet::new)</li>
<li>Collectors.toUnmodifiableSet()</li>
</ul>
<p>마지막의 List대신 Set을 붙여준다는 차이만 있기 때문에, 예시 코드만 첨부하겠습니다.</p>
<pre><code class="language-java">@Test
void toSet() {
    Set&lt;Integer&gt; result = numbers.stream()
                .collect(Collectors.toSet());

    assertThat(result).containsExactly(1, 2, 3);
}

@Test
void toCollection() {
    LinkedHashSet&lt;Integer&gt; result = numbers.stream()
                .collect(Collectors.toCollection(LinkedHashSet::new));

    assertThat(result).containsExactly(1, 2, 3);
}

@Test
void toUnmodifiableSet() {
    Set&lt;Integer&gt; result = numbers.stream()
                .collect(Collectors.toUnmodifiableSet());

    assertThatThrownBy(() -&gt; result.add(4))
                .isInstanceOf(UnsupportedOperationException.class);
}</code></pre>
<hr>
<h3 id="collectorscollectingandthen">Collectors.collectingAndThen()</h3>
<p>이번에 새로 알게 된 기능 중 굉장히 유용하게 사용할 수 있을 거 같은 것 중 하나입니다.</p>
<blockquote>
<p>collecting을 먼저 진행한 결과를 통해 새로운 값을 만들 수 있음!!</p>
</blockquote>
<p>이해하기 쉽게 예제 클래스를 제가 만들어보았습니다.</p>
<pre><code class="language-java">class Score {
    private final List&lt;Integer&gt; numbers;

    public Score(List&lt;Integer&gt; numbers) {
        this.numbers = numbers;
    }

    public List&lt;Integer&gt; getNumbers() {
        return numbers;
    }
}</code></pre>
<p>다음과 같이 List&lt;Integer&gt;를 통해 생성할 수 있는 Score 객체가 있다고 과정해봅시다.</p>
<pre><code class="language-java">@Test
void collectingAndThen() {
    List&lt;String&gt; source = List.of(&quot;1&quot;, &quot;2&quot;, &quot;3&quot;, &quot;4&quot;);

    Score score = source.stream()
                .map(Integer::parseInt)
                .collect(Collectors.collectingAndThen(Collectors.toList(), Score::new));
                //변환된 값들을 List로 만들고, 이 값을 통해 Score 객체를 생성한다.

    assertThat(score.getNumbers()).containsExactly(1, 2, 3, 4);
}</code></pre>
<p>그럼 다음과 같이 변환된 값들을 List로 만들고, 이 값을 통해 Score 객체를 생성할 수 있습니다.</p>
<p>굉장히 유용하다 생각이 들었고, 여러 곳에서 적용하여 사용해보자 생각이 들었습니다.</p>
<hr>
<h3 id="📝-마치며">📝 마치며</h3>
<p>아직도 모르는 기능이 많고, 얼른 배우고 싶다는 생각이 가득해지는 것 같습니다.
꾸준히 성장하는 개발자가 되길~~</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[split 메서드 limit 파라미터]]></title>
            <link>https://velog.io/@jcoding-play/split-%EB%A9%94%EC%84%9C%EB%93%9C-limit-%ED%8C%8C%EB%9D%BC%EB%AF%B8%ED%84%B0</link>
            <guid>https://velog.io/@jcoding-play/split-%EB%A9%94%EC%84%9C%EB%93%9C-limit-%ED%8C%8C%EB%9D%BC%EB%AF%B8%ED%84%B0</guid>
            <pubDate>Wed, 01 Nov 2023 08:56:50 GMT</pubDate>
            <description><![CDATA[<h2 id="split-메서드">split 메서드</h2>
<hr>
<h3 id="limit-파라미터-그게-뭐야">limit 파라미터?? 그게 뭐야!?!</h3>
<pre><code class="language-java">public String[] split(String regex, int limit)</code></pre>
<p>split 메서드에서 <code>limit</code> 값을 파라미터로 전달 받을 수 있다는 것을 알게 되었다.</p>
<blockquote>
<p><em>여태 limit 파라미터의 존재조차 몰랐습니다...</em></p>
</blockquote>
<hr>
<h3 id="limit--0">limit == 0</h3>
<p>차이가 있을까??</p>
<p>내가 일반적으로 사용했던 split 메서드는 <code>split(String regex)</code>였고, 이것은 limit 값에 0이 생략된 것이였다.    </p>
<blockquote>
<p>다음과 같이 사용했을때 발생했던 문제를 하나 코드를 통해 봐보자!</p>
</blockquote>
<pre><code class="language-java">@Test
void splitTest(){
    String input=&quot;길동,,&quot;;
    String[]result=input.split(&quot;,&quot;);

    assertThat(result).containsExactly(&quot;길동&quot;);
}</code></pre>
<p>내가 기대했던 결과는 <code>{&quot;길동&quot;, &quot;&quot;, &quot;&quot;}</code>과 같이 값을 가지는 것이었다.<br>하지만 기대했던 결과와 다르게 <code>{&quot;길동&quot;}</code>만 포함하는 것을 볼 수 있다.   </p>
<p>이유는 limit 값이 0일 때, <strong>맨 뒤의 값들이 빈 값이라면 이를 제거</strong>하기 때문이다.</p>
<hr>
<h3 id="limit--0-1">limit &lt; 0</h3>
<p>이를 해결하기 위해, limit 파라미터에 음수값을 전달하여 맨 뒤의 값들을 전부 가져올 수 있다.</p>
<pre><code class="language-java">@Test
void splitTest(){
    String input = &quot;길동,,&quot;;
    String[] result = input.split(&quot;,&quot;, -1);

    assertThat(result).containsExactly(&quot;길동&quot;, &quot;&quot;, &quot;&quot;);
}</code></pre>
<p>다음과 같이 limit 파라미터에 음수 값을 전달하여 빈 값이여도 모두 전달할 수 있다.</p>
<hr>
<h3 id="limit--0-2">limit &gt; 0</h3>
<p>또한 만약 limit 파라미터에 0보다 큰 값을 전달하면, 전달한 크기만큼 문자를 나눈다.</p>
<pre><code class="language-java">@Test
void splitTest(){
    String input = &quot;길동,순신,현수&quot;;
    String[] result = input.split(&quot;,&quot;, 2);

    assertThat(result).containsExactly(&quot;길동&quot;, &quot;순신,현수&quot;);
}</code></pre>
<hr>
<blockquote>
<p>모두 split의 기능을 확실히 알고 사용하시길 바랍니다!~~ 
저처럼 몰라서 해결하려고 몇 시간 고민하지 마세요..</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[우아한테크코스 6기] 프리코스 1주차 회고]]></title>
            <link>https://velog.io/@jcoding-play/%EC%9A%B0%EC%95%84%ED%95%9C%ED%85%8C%ED%81%AC%EC%BD%94%EC%8A%A4-6%EA%B8%B0-%ED%94%84%EB%A6%AC%EC%BD%94%EC%8A%A4-1%EC%A3%BC%EC%B0%A8-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@jcoding-play/%EC%9A%B0%EC%95%84%ED%95%9C%ED%85%8C%ED%81%AC%EC%BD%94%EC%8A%A4-6%EA%B8%B0-%ED%94%84%EB%A6%AC%EC%BD%94%EC%8A%A4-1%EC%A3%BC%EC%B0%A8-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Mon, 30 Oct 2023 11:57:04 GMT</pubDate>
            <description><![CDATA[<p>드디어 기다리던 프리코스 1주차를 10/19 ~ 10/25까지 진행하였습니다.</p>
<p>저번 기수인 5기부터 우테코에 지원을 하기만 하면 프리코스를 함께 참여할 수 있었기에 코딩 테스트를 하지 않고 프리코스에 참여할 수 있었습니다.</p>
<h2 id="1주차">1주차</h2>
<hr>
<h3 id="숫자-야구-게임">숫자 야구 게임</h3>
<p>1주차의 미션은 <strong>숫자 야구 게임</strong>을 구현하는 것이였습니다.</p>
<p>미션을 진행하는 방식은 다음과 같이 나와있었습니다.</p>
<blockquote>
<h3 id="진행-방식">진행 방식</h3>
</blockquote>
<ul>
<li>미션은 <strong>기능 요구 사항, 프로그래밍 요구 사항, 과제 진행 요구 사항</strong> 세가지로 구성되어 있다.</li>
<li>세 개의 요구 사항을 만족하기 위해 노력한다. <strong>특히 기능을 구현하기 전에 기능 목록을 만든다.</strong></li>
<li>기능 요구 사항에 기재되지 않은 내용은 <strong>스스로 판단하여 구현</strong>한다.</li>
</ul>
<h3 id="요구-사항-분석">요구 사항 분석</h3>
<hr>
<p>미션 진행 방식에 명시되어 있는대로 기능을 구현하기 전에 기능 목록을 작성하기 위해 요구 사항을 꼼꼼히 읽고 기능 목록을 작성하기 위해 노력했습니다.</p>
<p><img src="https://velog.velcdn.com/images/jcoding-play/post/665ba10f-83e4-4aaa-975f-2453d589c4ad/image.png" alt=""></p>
<p>이번 미션에 나와있는 기능 요구 사항은 다음과 같았습니다. </p>
<p>이를 토대로 최대한 기능을 작은 단위로 분리하였고, 아래 사진은 제가 작성한 구현 기능 목록입니다.</p>
<p><img src="https://velog.velcdn.com/images/jcoding-play/post/f111c9ba-dc1b-4104-a66c-374af4d71200/image.png" alt=""></p>
<p>구현 기능 목록을 작성할때 구현할 클래스도 생각하며 분리하였고, 이를 통해 미션을 진행할 때는 클래스에 대한 고민을 줄일 수 있었습니다.
<em>(물론 구현 기능 목록을 작성할때부터 클래스를 생각하는 것이 좋은 것인지 모르겠습니다.)</em></p>
<h3 id="기능-구현">기능 구현</h3>
<hr>
<h4 id="클래스와-패키지-분리">클래스와 패키지 분리</h4>
<p>저는 다음과 같이 클래스와 패키지를 분리하였습니다.
<img src="https://velog.velcdn.com/images/jcoding-play/post/4992b0a0-b3c1-44f0-906a-1ab6a3566f96/image.png" alt=""></p>
<h4 id="테스트-코드">테스트 코드</h4>
<p>기능을 구현할 때 TDD 기반으로 진행하면서 대부분의 기능에 대해 테스트를 작성할 수 있었습니다.</p>
<blockquote>
<p>TDD와 리팩토링에 대해 궁금하신 분들은 한번씩 보시는 것을 추천드립니다. 
우테코의 대장이신 포비님이 강의하시는 영상입니다.
<a href="https://www.youtube.com/watch?v=bIeqAlmNRrA">https://www.youtube.com/watch?v=bIeqAlmNRrA</a></p>
</blockquote>
<p>테스트 코드를 작성할 때 귀찮기도 했지만, 발생할 수 있는 모든 예외 상황에 대해 생각하고 여러 입력 값에 대해 테스트를 진행하려고 노력했습니다. 특히, 에러가 발생할 확률이 높은 경곗값은 주의 깊게 테스트를 진행하였습니다.</p>
<p><img src="https://velog.velcdn.com/images/jcoding-play/post/cc08f518-e9c4-4e42-af6d-476e88374a66/image.png" alt=""></p>
<p>위에 테스트 코드는 경곗값을 모두 비교하기 위해 제가 작성했던 코드입니다.</p>
<blockquote>
<ul>
<li>1과 9에 대한 숫자를 입력했을 때는 예외가 발생 X</li>
</ul>
</blockquote>
<ul>
<li>0과 10에 대한 숫자를 입력했을 때는 예외가 발생 O</li>
</ul>
<p>실제로 TDD 기반으로 구현을 시작하면서 작성한 모든 로직들에 대해 테스트 코드를 구현하고 잘못된 설계와 버그를 초기에 수정할 수 있었습니다.</p>
<p><img src="https://velog.velcdn.com/images/jcoding-play/post/cb19527c-b3ea-4c9e-831d-43357cf973a8/image.png" alt=""></p>
<p>보기만 해도 맘이 편안해지는 초록색과 총 38개의 테스트 코드를 통해 꼼꼼히 기능에 대한 결과를 확인할 수 있었습니다.</p>
<h4 id="원시값-포장">원시값 포장</h4>
<p>숫자 야구 게임에서 공을 비교하기 위해 Ball이라는 객체를 만들었습니다.</p>
<pre><code class="language-java">// 처음에 작성했던 Ball 객체
public class Ball {
    private static final int MIN_BALL_NUMBER = 1;
    private static final int MAX_BALL_NUMBER = 9;
    private static final int MIN_POSITION = 1;
    private static final int MAX_POSITION = 3;

    private final int ballNumber;
    private final int position;

    public Ball(int ballNumber, int position) {
        validateBall(ballNumber, position);
        this.ballNumber = ballNumber;
        this.position = position;
    }

    private void validateBall(int ballNumber, int position) {
        validateBallNumber(ballNumber);
        validatePosition(position);
    }

    private void validateBallNumber(int ballNumber) {
        if (ballNumber &lt; MIN_BALL_NUMBER || ballNumber &gt; MAX_BALL_NUMBER) {
            throw new IllegalArgumentException(&quot;공의 숫자는 1에서 9사이의 값이어야 합니다.&quot;);
        }
    }

    private void validatePosition(int position) {
        if (position &lt; MIN_POSITION || position &gt; MAX_POSITION) {
            throw new IllegalArgumentException(&quot;공의 위치는 1에서 3사이의 값이어야 합니다.&quot;);
        }
    }
}</code></pre>
<p>하지만 대충 봐도 알 수 있듯이, 절반 이상의 코드가 공의 숫자와, 위치를 검증하는 코드로 이루어져있습니다. </p>
<p>이를 해결하기 위해 저는 공의 숫자와 위치를 각각 BallNumber, Position이라는 객체로 포장을 해주었습니다.</p>
<pre><code class="language-java">public class Ball {
    private final BallNumber ballNumber;
    private final Position position;

    public Ball(int ballNumber, int position) {
        this.ballNumber = new BallNumber(ballNumber);
        this.position = new Position(position);
    }
}

public class BallNumber {
    private static final int MIN_BALL_NUMBER = 1;
    private static final int MAX_BALL_NUMBER = 9;

    private final int ballNumber;

    public BallNumber(int ballNumber) {
        validateBallNumber(ballNumber);
        this.ballNumber = ballNumber;
    }

    private void validateBallNumber(int ballNumber) {
        if (ballNumber &lt; MIN_BALL_NUMBER || ballNumber &gt; MAX_BALL_NUMBER) {
            throw new IllegalArgumentException(&quot;공의 숫자는 1에서 9사이의 값이어야 합니다.&quot;);
        }
    }
}

public class Position {
    private static final int MIN_POSITION = 1;
    private static final int MAX_POSITION = 3;

    private final int position;

    public Position(int position) {
        validatePosition(position);
        this.position = position;
    }

    private void validatePosition(int position) {
        if (position &lt; MIN_POSITION || position &gt; MAX_POSITION) {
            throw new IllegalArgumentException(&quot;공의 위치는 1에서 3사이의 값이어야 합니다.&quot;);
        }
    }
}</code></pre>
<p>원시값을 포장함으로써 Ball 객체가 가지던 많은 유효성 검사와 관련된 코드들을 다른 객체로 위임할 수 있었고, Ball 객체는 그와 관련된 책임만을 가질 수 있었습니다.</p>
<h3 id="리팩토링">리팩토링</h3>
<hr>
<p>요구사항에 맞게 모든 기능을 구현하고 난 뒤에는 리팩토링에 많은 시간을 투자하였습니다.</p>
<p>중심적으로 본 부분들은 다음과 같습니다.</p>
<blockquote>
<ul>
<li>단일 책임의 원칙을 지키도록 리팩토링한다.</li>
</ul>
</blockquote>
<ul>
<li>메서드를 작은 단위로 분리할 수 있는 부분을 찾아 분리한다.</li>
<li>변수명, 메서드명, 클래스명 등 가독성을 높일 수 있는 이름으로 바꾼다.</li>
<li>객체 필드에 직접 접근하는 것이 아니라 객체에 메시지를 보내 기능을 수행한다.</li>
<li>람다 함수를 활용 가능한 부분을 찾아 적용한다.</li>
</ul>
<p>끊임없이 더 나은 코드가 있을지 고민하며 리팩토링해보면서, 시간을 많이 투자하긴 했지만 다양한 개념들을 새롭게 알고 습득하면서 굉장히 의미 있는 시간이라 느꼈다. </p>
<p><em>오히려 리팩토링을 하며 더 많이 느끼고, 배울 수 있어서 뿌듯하기도 했다.</em></p>
<h3 id="마치며">마치며</h3>
<hr>
<p>굉장히 오래 기다리고 목표로 삼았던 우테코가 시작하면서 떨리기도 하고, 기쁘기도 했다. 이번 1주차 미션을 통해 새로운 개념들을 알 수 있었고, 많은 부분들을 배울 수 있었다. </p>
<p>이를 통해 스스로 성장하길 바라는 욕심이 더욱 커져 가기도 했고, 항상 고민하고 끊임없이 성장하는 개발자가 될 수 있길 바란다...!!</p>
<blockquote>
<p>자세한 코드는 여기서 확인 가능합니다!!
<a href="https://github.com/jcoding-play/java-baseball-6/tree/gyungchan">https://github.com/jcoding-play/java-baseball-6/tree/gyungchan</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Java - Record ]]></title>
            <link>https://velog.io/@jcoding-play/Java-Record</link>
            <guid>https://velog.io/@jcoding-play/Java-Record</guid>
            <pubDate>Wed, 25 Oct 2023 14:40:08 GMT</pubDate>
            <description><![CDATA[<h2 id="record란">Record란</h2>
<ul>
<li>불변 데이터 객체를 쉽게 생성해주는 새로운 클래스이다.</li>
</ul>
<p>잘 사용할 수 있다면 확실히 코드가 깔끔해지고, 편리하게 사용 가능할 것 같아 보입니다.</p>
<p>그럼 Record 클래스를 이해하기 쉽게 코드를 통해 알아봅시다.</p>
<pre><code class="language-java">public class User {
    private final String name;
    private final int age;

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

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
}
</code></pre>
<p>위와 같이 User라는 객체가 있고, 이를 나타내는 변수로 name, age가 있다고 해보자.
이 코드를 record로 한번 바꿔보면 다음과 같다.</p>
<pre><code class="language-java">public record User(String name, int age) {
}</code></pre>
<p>놀랍게도 이렇게 간단히 구현이 완료된다. 
심지어 equals, hashcode, toString을 자동으로 생성해주기 때문에 더 많은 기능을 제공해준다고 말할 수 있다.</p>
<h3 id="record-구조">Record 구조</h3>
<hr>
<p>레코드 클래스의 구조는 위에 코드를 통해 알 수 있듯이,
<code>class</code> 대신 <code>record</code>라 선언하고 <code>레코드명(헤더) {바디}</code> 구조를 이루고 있다.</p>
<p>이렇게 축약할 수 있는 이유는 컴파일러가 헤더를 통해 내부 필드를 추론하기 때문인데, 추론한 내부 필드를 통해 자동으로 equals, hashCode, toString, getter 메서드를 생성해주는 것이다.</p>
<p>여기서 한가지 주의할 점이 있는데, getter 메서드는 필드명을 통해 사용한다.</p>
<pre><code class="language-java">// 일반적인 getter 메서드
String name = user.getName();

// Record 클래스에서는 필드명으로 접근
String name = user.name();</code></pre>
<h3 id="record-특징">Record 특징</h3>
<hr>
<p>이러한 레코드의 특징으로는,</p>
<ul>
<li>헤더를 통해 각 필드를 추론하고, private final로 선언된다.</li>
<li>getter, equals, hashcode, toString이 자동으로 생성된다.</li>
<li>다른 클래스를 상속하거나/상속시킬 수 없다.</li>
<li>레코드 내부에 멤버 변수를 선언할 수 없다.</li>
</ul>
<h3 id="📝-마치면서">📝 마치면서</h3>
<hr>
<p>아직 많이 사용해보진 않았지만 Record 클래스에 익숙해져서 잘 사용해지길 바라며 여기서 글을 마무리하겠습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[자바 인터페이스의 default method와 private method]]></title>
            <link>https://velog.io/@jcoding-play/%EC%9E%90%EB%B0%94-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4%EC%9D%98-default-method%EC%99%80-private-method</link>
            <guid>https://velog.io/@jcoding-play/%EC%9E%90%EB%B0%94-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4%EC%9D%98-default-method%EC%99%80-private-method</guid>
            <pubDate>Wed, 25 Oct 2023 09:45:02 GMT</pubDate>
            <description><![CDATA[<h2 id="default-method">Default Method</h2>
<p>모두 자바 interface에서 이용 가능한 default method를 아시나요?
최근에 알게된 default method에 대해서 작성해보려고 합니다.</p>
<hr>
<p>이전까지 인터페이스를 구현하면 인터페이스를 구현할 클래스를 생성하고, 이 클래스 안에서 메서드를 오버라이드 해서 작성했었다.</p>
<p>그런데 만약 인터페이스를 사용하는 클래스가 100개가 있고, 인터페이스에 공통적으로 사용 가능한 메서드 하나를 추가했다고 해보자.</p>
<ul>
<li>이런 상황이 있을 때 문제는??<blockquote>
<p>클래스 100개에 동일한 코드를 작성해야됨!!</p>
</blockquote>
</li>
</ul>
<p>모든 클래스에 열심히 복사, 붙여넣기를 통해 코드를 추가할 수 있겠지만, 대충 생각해봐도 이것은 너무나도 번거롭고 귀찮은 작업이 될 것 같다.</p>
<hr>
<h3 id="이를-해결할-방법이-있을까">이를 해결할 방법이 있을까?</h3>
<p>이를 해결하기 위한 방법이 바로 설명 드리는 default method이다.
default method는 인터페이스 안에서 작성 가능하고, 구현한 클래스에게 모두 동일하게 적용할 수 있다.</p>
<p>즉, 클래스 100개에 모든 코드를 복사하지 않고 인터페이스에서 default method를 통해 간단히 해결 가능하다!!</p>
<ul>
<li>아래와 같은 코드가 있다.</li>
</ul>
<pre><code class="language-java">public interface MyInterface {
    // 일반적인 인터페이스 활용 방법
    void printMessage();

    // 디폴트 메서드를 이용한 방법
    default String getMessage() {
        return &quot;디폴트 메서드를 이용한 메시지입니다.&quot;;
    }
}

public class Main implements MyInterface {

    @Override
    public void printMessage() {
        System.out.println(&quot;일반적인 인터페이스 활용&quot;);
    }
}</code></pre>
<p>위와 같은 코드가 있을 때 실제로 Main 클래스에서 구현하지 않아도 MyInterface 안에 있는 getMessage() 메서드를 이용 가능하다는 것을 볼 수 있다.
<img src="https://velog.velcdn.com/images/jcoding-play/post/d7e8d63c-c575-4228-bf88-21d70f1b8a13/image.png" alt=""></p>
<hr>
<h2 id="interface에서-private-method">Interface에서 private method</h2>
<p>또한 인터페이스에서 private method를 이용할 수 있다.</p>
<p>우리가 클래스를 작성했을 때 중복되는 기능을 효율적으로 관리하기 위한 방법이 무엇이 있을까?</p>
<p>여러가지가 있겠지만, 간단하게 메서드로 분리하는 방법을 생각할 수 있다.</p>
<p>이와 동일하게 인터페이스 안에서도 private method를 이용하여 중복을 제거할 수 있고, 당연하게 해당 인터페이스를 사용하는 클래스들은 private method를 사용할 수 없다.</p>
<pre><code class="language-java">// 예시 코드
public interface MyInterface {

    void printMessage();

    default String getMessage() {
        return privateMethod();
    }

    default String getMessage(String input) {
        return String.format(&quot;%s: %s&quot;, input, privateMethod());
    }

    // private method를 이용한 중복 제거
    private String privateMethod() {
        return &quot;디폴트 메서드를 이용한 메시지입니다.&quot;;
    }
}</code></pre>
<hr>
<p>정말 공부하다 보면 모르던 개념들을 너무나도 많이 알 수 있고, 잘 활용해봐야겠다는 생각이 드는 것 같다. </p>
<p>얼른 익숙해질 수 있길~</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[자바 컬렉션에서 불변 객체 생성]]></title>
            <link>https://velog.io/@jcoding-play/%EC%9E%90%EB%B0%94-%EC%BB%AC%EB%A0%89%EC%85%98%EC%97%90%EC%84%9C-%EB%B6%88%EB%B3%80-%EA%B0%9D%EC%B2%B4-%EC%83%9D%EC%84%B1</link>
            <guid>https://velog.io/@jcoding-play/%EC%9E%90%EB%B0%94-%EC%BB%AC%EB%A0%89%EC%85%98%EC%97%90%EC%84%9C-%EB%B6%88%EB%B3%80-%EA%B0%9D%EC%B2%B4-%EC%83%9D%EC%84%B1</guid>
            <pubDate>Wed, 25 Oct 2023 09:08:50 GMT</pubDate>
            <description><![CDATA[<h2 id="collection-factory-method">Collection Factory Method</h2>
<p>컬렉션에서 불변 객체를 생성하는 기능이 추가되어 공부하게 되었다.
여기서 불변 객체란 한번 할당하면 내부 데이터를 수정할 수 없는 것을 말한다.</p>
<ul>
<li>불변 객체를 생성하는 방법: of() 정적 메서드를 이용</li>
</ul>
<pre><code class="language-java">// 불변 컬렉션을 생성함
List&lt;Integer&gt; result = List.of(1, 2, 3);

Map&lt;String, Integer&gt; result = Map.of(
                &quot;1&quot;, 1,
                &quot;2&quot;, 2,
                &quot;3&quot;, 3
);</code></pre>
<p>위와 같이 간편하게 불변 컬렉션을 만들 수 있다.</p>
<p>하지만 주의점이 무엇이 있을까??</p>
<blockquote>
<p>내부 데이터를 수정하려고 하면 UnsupportedOperationException이 발생!!</p>
</blockquote>
<p>실제로 of() 메서드를 이용하여 생성한 List에 값을 추가, 제거, 정렬 등 내부 데이터를 수정하려고 하면 예외가 발생한다는 것을 테스트 코드를 통해 확인할 수 있었다.
<img src="https://velog.velcdn.com/images/jcoding-play/post/d132a696-37a8-40e0-b5b4-62fb8949603a/image.png" alt=""></p>
<hr>
<h2 id="이를-해결할-방법이-있을까">이를 해결할 방법이 있을까?</h2>
<p>생성한 컬렉션에 대해 내부 데이터를 수정하고 싶다면 재할당을 통해 해결할 수 있다.
<img src="https://velog.velcdn.com/images/jcoding-play/post/8b8acdb0-6828-40e0-8ffc-9dfd3fee9724/image.png" alt=""></p>
<p>of 메서드를 통해 만든 불변 컬렉션에서 내부 데이터를 수정할 때 예외가 발생했지만 다음과 같이 재할당을 하여 내부 데이터를 수정할 수 있었다.</p>
<pre><code class="language-java">List&lt;Integer&gt; source = List.of(1, 2, 3);

//이 부분이 핵심
List&lt;Integer&gt; numbers = new ArrayList&lt;&gt;(source);</code></pre>
<hr>
<h2 id="빈-불변-객체-생성">빈 불변 객체 생성</h2>
<p>또한 빈 불변 컬렉션을 제공하는 기능도 제공한다.</p>
<pre><code class="language-java">// 비어있는 불변 컬렉션을 생성함
List&lt;Integer&gt; result = Collections.emptyList();</code></pre>
<p>여기서도 마찬가지로 내부 데이터를 수정하는 것은 예외가 발생한다.<img src="https://velog.velcdn.com/images/jcoding-play/post/2a2509ed-b968-4edd-9090-b1d57241f979/image.png" alt=""></p>
<hr>
<p>허접한 글입니다만.. 조금이라도 도움이 되길 바라며 작성해보았습니다.</p>
<blockquote>
<p>이처럼 간단하게 불변 객체를 생성할 수 있지만, 주의할 점에 대해 잘 알고 사용하는 내가 되길 바라며...</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[트랜잭션 이해하기]]></title>
            <link>https://velog.io/@jcoding-play/%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@jcoding-play/%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 01 Sep 2023 17:44:06 GMT</pubDate>
            <description><![CDATA[<h2 id="트랜잭션transaction이란">트랜잭션(Transaction)이란?</h2>
<hr>
<p>트랜잭션을 해석하면 거래라는 뜻인데, 트랜잭션은 데이터베이스에 실행한 로직을 안전하게 처리하도록 보장해준다.</p>
<p>예를 들어보자, 만약 <u>A라는 사람이 B라는 사람에게 10000원을 이체</u>해준다고 가정해보자. 이체가 성공적으로 마무리 되었다면 문제가 없겠지만 만약 A라는 사람이 송금은 완료되었는데 B라는 사람이 전달받을 때 문제가 생기게 된다면 A라는 사람의 돈만 10000원이 사라지게 된다.</p>
<p>이러한 문제를 방지하기 위해 사용하는 것이 <strong>트랜잭션</strong>이라는 기술이다.
트랜잭션을 사용해 계좌이체를 성공했다면 모든 작업을 정상 반영하도록 <code>commit</code>을 해주고, 작업 중간에 실패했다면 <code>rollback</code>하여 사용자들에게 문제가 없도록 해준다.</p>
<p>그래서 트랜잭션은 ACID라고 하는 것을 보장해야 한다.</p>
<ul>
<li><strong>원자성</strong>(Atomicity)</li>
<li><strong>일관성</strong>(Consistency)</li>
<li><strong>격리성</strong>(Isolation)</li>
<li><strong>지속성</strong>(Durability)</li>
</ul>
<hr>
<h2 id="트랜잭션-적용">트랜잭션 적용</h2>
<p>트랜잭션은 먼저 비즈니스 로직이 있는 서비스 계층에서 시작해야 한다. 만약 비즈니스 로직이 잘못되면 롤백해야 하기 때문이다.</p>
<p><strong><em>그럼 트랜잭션을 적용하기 위해 어떻게 해야할까?</em></strong></p>
<ol>
<li>트랜잭션을 적용하기 위해서는 커넥션이 필요하고, 비즈니스 로직에서는 같은 커넥션을 유지해야만 트랜잭션을 적용할 수 있다. 그래서 가장 원시적인 방법으로 같은 비즈니스 로직에서 수행하는 모든 메서드에 커넥션을 매개변수로 전달하는 방법이 있다.</li>
</ol>
<pre><code class="language-java">public class Service {
    private final DataSource dataSource;
    private final Repository repository;

    public Service(DataSource dataSource, Repository repository) {
        this.dataSource = dataSource;
        this.repository = repository;
    }

    public void accountTransfer(String fromId, String toId, int money) throws SQLException {
         // 같은 커넥션을 사용하도록 커넥션을 서비스 계층에서 생성
        Connection con = dataSource.getConnection();
        try {
            con.setAutoCommit(false);
            // 커넥션을 매개변수로 전달
            bizLogic(con, fromId, toId, money); 
            con.commit();
        } catch (Exception e) {
            con.rollback();
            throw new IllegalStateException(e);
        } finally {
            release(con);
        }
    }

    private void bizLogic(Connection con, String fromId, String toId, int money) throws SQLException {
        ...
    }

    private void release(Connection con) {
        if (con != null) {
            try {
                con.setAutoCommit(true);
                con.close();
            } catch (Exception e) {
                log.info(&quot;error&quot;, e);
            }
        }
    }
}
</code></pre>
<pre><code class="language-java">// Connection을 매개변수로 전달하면서 사용
public void method1(Connection connection, ...) { 
    run();
}</code></pre>
<ul>
<li>하지만 이러한 방법은 계속 Connection을 전달해야 하는 번거로움도 있고, 
  서비스 로직에서도 트랜잭션을 적용하기 위해 코드가 지저분해져서 잘 사용        하는 방법은 아니다.</li>
</ul>
<ol start="2">
<li><p>두번째 방법은 트랜잭션 매니저와 트랜잭션 동기화 매니저를 통해 커넥션을 맺는 방법이다. </p>
<p> 동작 방식은</p>
<ul>
<li><p>트랜잭션 매니저가 데이터소스를 통해 미리 커넥션을 만들고 트랜잭션을 시작한다.</p>
<pre><code class="language-java">transactionManager.getTransaction()</code></pre>
</li>
<li><p>트랜잭션 매니저가 트랜잭션이 시작된 커넥션을 트랜잭션 동기화 매니저에 보관한다.</p>
</li>
<li><p>repository에서 트랜잭션 동기화 매니저에 보관된 커넥션을 꺼내서 사용한다. 따라서 파라미터로 커넥션을 전달하지 않아도 된다.</p>
<pre><code class="language-java">// 트랜잭션 동기화 매니저에 보관된 커넥션을 연결
Connection connection =  DataSourceUtils.getConnection(dataSource); 

//트랜잭션을 사용하기 위해 동기화된 커넥션을 닫지 않고 유지
DataSourceUtils.releaseConnection(connection, dataSource)</code></pre>
</li>
<li><p>트랜잭션이 종료되면 트랜잭션 매니저는 트랜잭션 동기화 매니저에 보관된 커넥션을 통해 트랜잭션을 종료하고, 커넥션을 닫는다.</p>
</li>
</ul>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[데이터 베이스 커넥션 획득 방법 - DataSource]]></title>
            <link>https://velog.io/@jcoding-play/%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EC%BB%A4%EB%84%A5%EC%85%98-%ED%9A%8D%EB%93%9D-%EB%B0%A9%EB%B2%95-DataSource</link>
            <guid>https://velog.io/@jcoding-play/%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EC%BB%A4%EB%84%A5%EC%85%98-%ED%9A%8D%EB%93%9D-%EB%B0%A9%EB%B2%95-DataSource</guid>
            <pubDate>Wed, 30 Aug 2023 16:41:31 GMT</pubDate>
            <description><![CDATA[<h4 id="기본적인-데이터-베이스-커넥션을-획득하기-위한-방법은-다음과-같다">기본적인 데이터 베이스 커넥션을 획득하기 위한 방법은 다음과 같다.</h4>
<blockquote>
<ol>
<li>DB 드라이버를 통해 커넥션을 조회한다.</li>
<li>DB 드라이버는 DB와 TCP/IP 커넥션을 연결한다.</li>
<li>커넥션이 연결되면 ID, PASSWORD 등 부가 정보를 전달한다.</li>
<li>DB는 ID,PASSWORD를 통해 내부 인증을 완료하고, 내부에 DB 세션을 생성한다.</li>
<li>DB는 커넥션 생성이 완료되었다는 응답을 보낸다.</li>
<li>DB 드라이버는 커넥션 객체를 생성해서 클라이언트에 반환한다.</li>
</ol>
</blockquote>
<p>실제로 아래와 같은 코드를 통해 매번 새로운 커넥션을 획득할 수 있다.</p>
<pre><code class="language-java">public Connection getConnection() {
    Connection connection = DriverManager.getConnection(...);
}</code></pre>
<hr>
<p>하지만 이렇게 매번 커넥션을 새로 만들고 획득하는 방법은 시간이 많이 들기 때문에 이를 해결하기 위해 커넥션을 미리 생성해두고 사용하는 커넥션 풀이라는 방법이 사용한다.</p>
<h2 id="커넥션-풀">커넥션 풀</h2>
<p>커넥션 풀은 필요한 만큼 커넥션을 미리 확보해서 풀에 보관한 후 애플리케이션 로직에서 커넥션 풀을 통해 이미 생성되어 있는 커넥션을 가져다 쓰게 되고, 이를 통해 매번 새로운 커넥션을 만들고 획득하는 시간을 아낄 수 있다.</p>
<p>그런 후 커넥션을 모두 사용하고 나면 커넥션을 종료하는 것이 아니라, 다음에 다시 사용할 수 있도록 해당 커넥션을 그대로 커넥션 풀에 반환하게 된다.</p>
<hr>
<h2 id="datasource">DataSource</h2>
<p>스프링은 이러한 커넥션을 얻는 방법을 추상화를 통해 유연하게 구현해놓았다.</p>
<p>만약 우리가 처음 보았던 <code>DriverManager</code>를 통해 커넥션을 획득하다가 <code>커넥션 풀</code> 방식을 사용하도록 하려면 코드를 변경해야 한다는 것은 자연스런 생각이다.</p>
<p>하지만 자바는 <code>DataSource</code> 인터페이스를 통해 커넥션을 유연하게 획득할 수 있도록 구현해 놓았다.</p>
<p><strong>DataSource 핵심 기능 코드</strong></p>
<pre><code class="language-java">public interface DataSource {
    Connection getConnection() throws SQLException;
}</code></pre>
<p>이러한 추상화 덕분에 우리는 설정은 신경쓰지 않고 사용할 수 있고,
실제 설정할 때만 DriverManagerDataSource 또는 HikariDataSource를 통해 설정하여 커넥션을 획득할 수 있다.</p>
]]></description>
        </item>
    </channel>
</rss>